aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorBrooks J Rady <b.j.rady@gmail.com>2021-02-09 22:19:34 +0000
committerBrooks J Rady <b.j.rady@gmail.com>2021-02-09 22:19:34 +0000
commit979197085617a84992b38c7fd02098965e2479d5 (patch)
tree4136a1d4f39254e72379d1204b62ce6b1e54d144 /src
parentWork towards a functional build (diff)
downloadsl0ppij-979197085617a84992b38c7fd02098965e2479d5.tar.gz
sl0ppij-979197085617a84992b38c7fd02098965e2479d5.tar.xz
sl0ppij-979197085617a84992b38c7fd02098965e2479d5.zip
Well, it turns out that I spent 4 hours on something impossible...
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs29
-rw-r--r--src/client/boundaries.rs831
-rw-r--r--src/client/layout.rs220
-rw-r--r--src/client/mod.rs6
-rw-r--r--src/client/panes/grid.rs820
-rw-r--r--src/client/panes/mod.rs9
-rw-r--r--src/client/panes/plugin_pane.rs191
-rw-r--r--src/client/panes/terminal_character.rs572
-rw-r--r--src/client/panes/terminal_pane.rs675
-rw-r--r--src/client/tab.rs2004
-rw-r--r--src/common/command_is_executing.rs53
-rw-r--r--src/common/errors.rs288
-rw-r--r--src/common/input/actions.rs47
-rw-r--r--src/common/input/handler.rs349
-rw-r--r--src/common/input/keybinds.rs143
-rw-r--r--src/common/input/mod.rs3
-rw-r--r--src/common/ipc.rs46
-rw-r--r--src/common/mod.rs659
-rw-r--r--src/common/os_input_output.rs217
-rw-r--r--src/common/pty_bus.rs326
-rw-r--r--src/common/screen.rs201
-rw-r--r--src/common/utils/consts.rs6
-rw-r--r--src/common/utils/logging.rs76
-rw-r--r--src/common/utils/mod.rs3
-rw-r--r--src/common/utils/shared.rs19
-rw-r--r--src/common/wasm_vm.rs121
-rw-r--r--src/main.rs59
-rw-r--r--src/server/mod.rs5
-rw-r--r--src/tests/fakes.rs205
-rw-r--r--src/tests/fixtures/bash_cursor_linewrapbin0 -> 1346 bytes
-rw-r--r--src/tests/fixtures/clear_scroll_regionbin0 -> 5240 bytes
-rw-r--r--src/tests/fixtures/emacs_longbuf_tutorialbin0 -> 9781 bytes
-rw-r--r--src/tests/fixtures/fish_and_bandwhichbin0 -> 6272 bytes
-rw-r--r--src/tests/fixtures/fish_paste_multilinebin0 -> 2395 bytes
-rw-r--r--src/tests/fixtures/fish_select_tab_completion_optionsbin0 -> 3083 bytes
-rw-r--r--src/tests/fixtures/fish_tab_completion_optionsbin0 -> 2654 bytes
-rw-r--r--src/tests/fixtures/git_diff_scrollupbin0 -> 5174 bytes
-rw-r--r--src/tests/fixtures/git_logbin0 -> 2196 bytes
-rw-r--r--src/tests/fixtures/htopbin0 -> 16234 bytes
-rw-r--r--src/tests/fixtures/htop_right_scrollingbin0 -> 15949 bytes
-rw-r--r--src/tests/fixtures/htop_scrollingbin0 -> 18484 bytes
-rw-r--r--src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml16
-rw-r--r--src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml16
-rw-r--r--src/tests/fixtures/layouts/three-panes-with-nesting.yaml16
-rw-r--r--src/tests/fixtures/nvim_insertbin0 -> 2548 bytes
-rw-r--r--src/tests/fixtures/tab_charactersbin0 -> 633 bytes
-rw-r--r--src/tests/fixtures/vim_ctrl_dbin0 -> 3698 bytes
-rw-r--r--src/tests/fixtures/vim_ctrl_ubin0 -> 4994 bytes
-rw-r--r--src/tests/fixtures/vim_overwritebin0 -> 4154 bytes
-rw-r--r--src/tests/fixtures/vim_scroll_region_downbin0 -> 3829 bytes
-rw-r--r--src/tests/integration/basic.rs348
-rw-r--r--src/tests/integration/close_pane.rs647
-rw-r--r--src/tests/integration/compatibility.rs486
-rw-r--r--src/tests/integration/layouts.rs52
-rw-r--r--src/tests/integration/mod.rs14
-rw-r--r--src/tests/integration/move_focus_down.rs79
-rw-r--r--src/tests/integration/move_focus_left.rs79
-rw-r--r--src/tests/integration/move_focus_right.rs79
-rw-r--r--src/tests/integration/move_focus_up.rs79
-rw-r--r--src/tests/integration/resize_down.rs689
-rw-r--r--src/tests/integration/resize_left.rs670
-rw-r--r--src/tests/integration/resize_right.rs670
-rw-r--r--src/tests/integration/resize_up.rs685
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_largest_terminal_when_there_is_no_room.snap9
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_terminals_horizontally_when_active_terminal_is_too_small.snap9
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__max_panes.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__scrolling_down_inside_a_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__scrolling_up_inside_a_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__split_largest_terminal.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__split_terminals_horizontally.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__split_terminals_vertically.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__starts_with_one_terminal.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__basic__toggle_focused_pane_fullscreen.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_above_it.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_below_it.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_to_the_left.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_to_the_right.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_above_it.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_above_it_away_from_screen_edges.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_below_it.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_below_it_away_from_screen_edges.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_left.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_left_away_from_screen_edges.snap35
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_right.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_right_away_from_screen_edges.snap35
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__close_pane__closing_last_pane_exits_app.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__bash_cursor_linewrap.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__clear_scroll_region.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__display_tab_characters_properly.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__emacs_longbuf.snap65
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_paste_multiline.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_select_tab_completion_options.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_tab_completion_options.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__git_diff_scrollup.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__git_log.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_right_scrolling.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_scrolling.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__neovim_insert_mode.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__run_bandwhich_from_fish_shell.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_ctrl_d.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_ctrl_u.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_overwrite.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_scroll_region_down.snap33
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__layouts__accepts_basic_layout.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__move_focus_down__move_focus_down.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__move_focus_down__move_focus_down_to_the_largest_overlap.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left_to_the_largest_overlap.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right_to_the_largest_overlap.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__move_focus_up__move_focus_up.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__move_focus_up__move_focus_up_to_the_largest_overlap.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__cannot_resize_down_when_pane_below_is_at_minimum_height.snap10
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_multiple_panes_above.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_pane_above.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_pane_below.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_and_right_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_left_and_right.snap45
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_right_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_and_below.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_and_right_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_right.snap45
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_right_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__cannot_resize_left_when_pane_to_the_left_is_at_minimum_width.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_multiple_panes_to_the_left.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_pane_to_the_left.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_pane_to_the_right.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_above_and_below.snap45
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_and_right.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_above_and_below.snap45
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__cannot_resize_right_when_pane_to_the_left_is_at_minimum_width.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_multiple_panes_to_the_left.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_pane_to_the_left.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_pane_to_the_right.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_above_and_below.snap45
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_and_right.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_above_and_below.snap45
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__cannot_resize_up_when_pane_above_is_at_minimum_height.snap10
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_multiple_panes_above.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_pane_above.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_pane_below.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_and_right_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_and_right.snap45
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_right_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_and_below.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_and_right_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_right.snap45
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_right_with_current_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__tabs__close_last_pane_in_a_tab.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__tabs__close_tab.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__tabs__close_the_middle_tab.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__tabs__close_the_tab_that_has_a_pane_in_fullscreen.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__tabs__closing_last_tab_exits_the_app.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__tabs__open_new_tab.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__tabs__switch_to_next_tab.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__tabs__switch_to_prev_tab.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__toggle_fullscreen__adding_new_terminal_in_fullscreen.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__toggle_fullscreen__move_focus_is_disabled_in_fullscreen.snap25
-rw-r--r--src/tests/integration/tabs.rs322
-rw-r--r--src/tests/integration/toggle_fullscreen.rs77
-rw-r--r--src/tests/mod.rs5
-rw-r--r--src/tests/possible_tty_inputs.rs94
-rw-r--r--src/tests/tty_inputs.rs458
-rw-r--r--src/tests/utils.rs79
182 files changed, 16962 insertions, 0 deletions
diff --git a/src/cli.rs b/src/cli.rs
new file mode 100644
index 00000000..6fc5020b
--- /dev/null
+++ b/src/cli.rs
@@ -0,0 +1,29 @@
+use std::path::PathBuf;
+use structopt::StructOpt;
+
+#[derive(StructOpt, Debug, Default)]
+#[structopt(name = "mosaic")]
+pub struct CliArgs {
+ /// Send "split (direction h == horizontal / v == vertical)" to active mosaic session
+ #[structopt(short, long)]
+ pub split: Option<char>,
+
+ /// Send "move focused pane" to active mosaic session
+ #[structopt(short, long)]
+ pub move_focus: bool,
+
+ /// Send "open file in new pane" to active mosaic session
+ #[structopt(short, long)]
+ pub open_file: Option<PathBuf>,
+
+ /// Maximum panes on screen, caution: opening more panes will close old ones
+ #[structopt(long)]
+ pub max_panes: Option<usize>,
+
+ /// Path to a layout yaml file
+ #[structopt(short, long)]
+ pub layout: Option<PathBuf>,
+
+ #[structopt(short, long)]
+ pub debug: bool,
+}
diff --git a/src/client/boundaries.rs b/src/client/boundaries.rs
new file mode 100644
index 00000000..67733c12
--- /dev/null
+++ b/src/client/boundaries.rs
@@ -0,0 +1,831 @@
+use crate::tab::Pane;
+use std::collections::HashMap;
+
+use std::fmt::{Display, Error, Formatter};
+
+pub mod boundary_type {
+ pub const TOP_RIGHT: &str = "┐";
+ pub const VERTICAL: &str = "│";
+ pub const HORIZONTAL: &str = "─";
+ pub const TOP_LEFT: &str = "┌";
+ pub const BOTTOM_RIGHT: &str = "┘";
+ pub const BOTTOM_LEFT: &str = "└";
+ pub const VERTICAL_LEFT: &str = "┤";
+ pub const VERTICAL_RIGHT: &str = "├";
+ pub const HORIZONTAL_DOWN: &str = "┬";
+ pub const HORIZONTAL_UP: &str = "┴";
+ pub const CROSS: &str = "┼";
+}
+
+pub type BoundaryType = &'static str; // easy way to refer to boundary_type above
+
+#[derive(Clone, Copy, Debug)]
+pub struct BoundarySymbol {
+ boundary_type: BoundaryType,
+ invisible: bool,
+}
+
+impl BoundarySymbol {
+ pub fn new(boundary_type: BoundaryType) -> Self {
+ BoundarySymbol {
+ boundary_type,
+ invisible: false,
+ }
+ }
+ pub fn invisible(mut self) -> Self {
+ self.invisible = true;
+ self
+ }
+}
+
+impl Display for BoundarySymbol {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+ if self.invisible {
+ write!(f, " ")
+ } else {
+ write!(f, "{}", self.boundary_type)
+ }
+ }
+}
+
+fn combine_symbols(
+ current_symbol: BoundarySymbol,
+ next_symbol: BoundarySymbol,
+) -> Option<BoundarySymbol> {
+ let invisible = current_symbol.invisible || next_symbol.invisible;
+ let current_symbol = current_symbol.boundary_type;
+ let next_symbol = next_symbol.boundary_type;
+ match (current_symbol, next_symbol) {
+ (boundary_type::TOP_RIGHT, boundary_type::TOP_RIGHT) => {
+ // (┐, ┐) => Some(┐)
+ let boundary_type = boundary_type::TOP_RIGHT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_RIGHT, boundary_type::VERTICAL) => {
+ // (┐, │) => Some(┤)
+ let boundary_type = boundary_type::VERTICAL_LEFT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_RIGHT, boundary_type::HORIZONTAL) => {
+ // (┐, ─) => Some(┬)
+ let boundary_type = boundary_type::HORIZONTAL_DOWN;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_RIGHT, boundary_type::TOP_LEFT) => {
+ // (┐, ┌) => Some(┬)
+ let boundary_type = boundary_type::HORIZONTAL_DOWN;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_RIGHT, boundary_type::BOTTOM_RIGHT) => {
+ // (┐, ┘) => Some(┤)
+ let boundary_type = boundary_type::VERTICAL_LEFT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_RIGHT, boundary_type::BOTTOM_LEFT) => {
+ // (┐, └) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_RIGHT, boundary_type::VERTICAL_LEFT) => {
+ // (┐, ┤) => Some(┤)
+ let boundary_type = boundary_type::VERTICAL_LEFT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_RIGHT, boundary_type::VERTICAL_RIGHT) => {
+ // (┐, ├) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_RIGHT, boundary_type::HORIZONTAL_DOWN) => {
+ // (┐, ┬) => Some(┬)
+ let boundary_type = boundary_type::HORIZONTAL_DOWN;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_RIGHT, boundary_type::HORIZONTAL_UP) => {
+ // (┐, ┴) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_RIGHT, boundary_type::CROSS) => {
+ // (┐, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL, boundary_type::HORIZONTAL) => {
+ // (─, ─) => Some(─)
+ let boundary_type = boundary_type::HORIZONTAL;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL, boundary_type::VERTICAL) => {
+ // (─, │) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL, boundary_type::TOP_LEFT) => {
+ // (─, ┌) => Some(┬)
+ let boundary_type = boundary_type::HORIZONTAL_DOWN;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL, boundary_type::BOTTOM_RIGHT) => {
+ // (─, ┘) => Some(┴)
+ let boundary_type = boundary_type::HORIZONTAL_UP;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL, boundary_type::BOTTOM_LEFT) => {
+ // (─, └) => Some(┴)
+ let boundary_type = boundary_type::HORIZONTAL_UP;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL, boundary_type::VERTICAL_LEFT) => {
+ // (─, ┤) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL, boundary_type::VERTICAL_RIGHT) => {
+ // (─, ├) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL, boundary_type::HORIZONTAL_DOWN) => {
+ // (─, ┬) => Some(┬)
+ let boundary_type = boundary_type::HORIZONTAL_DOWN;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL, boundary_type::HORIZONTAL_UP) => {
+ // (─, ┴) => Some(┴)
+ let boundary_type = boundary_type::HORIZONTAL_UP;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL, boundary_type::CROSS) => {
+ // (─, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL, boundary_type::VERTICAL) => {
+ // (│, │) => Some(│)
+ let boundary_type = boundary_type::VERTICAL;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL, boundary_type::TOP_LEFT) => {
+ // (│, ┌) => Some(├)
+ let boundary_type = boundary_type::VERTICAL_RIGHT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL, boundary_type::BOTTOM_RIGHT) => {
+ // (│, ┘) => Some(┤)
+ let boundary_type = boundary_type::VERTICAL_LEFT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL, boundary_type::BOTTOM_LEFT) => {
+ // (│, └) => Some(├)
+ let boundary_type = boundary_type::VERTICAL_RIGHT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL, boundary_type::VERTICAL_LEFT) => {
+ // (│, ┤) => Some(┤)
+ let boundary_type = boundary_type::VERTICAL_LEFT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL, boundary_type::VERTICAL_RIGHT) => {
+ // (│, ├) => Some(├)
+ let boundary_type = boundary_type::VERTICAL_RIGHT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL, boundary_type::HORIZONTAL_DOWN) => {
+ // (│, ┬) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL, boundary_type::HORIZONTAL_UP) => {
+ // (│, ┴) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL, boundary_type::CROSS) => {
+ // (│, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_LEFT, boundary_type::TOP_LEFT) => {
+ // (┌, ┌) => Some(┌)
+ let boundary_type = boundary_type::TOP_LEFT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_LEFT, boundary_type::BOTTOM_RIGHT) => {
+ // (┌, ┘) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_LEFT, boundary_type::BOTTOM_LEFT) => {
+ // (┌, └) => Some(├)
+ let boundary_type = boundary_type::VERTICAL_RIGHT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_LEFT, boundary_type::VERTICAL_LEFT) => {
+ // (┌, ┤) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_LEFT, boundary_type::VERTICAL_RIGHT) => {
+ // (┌, ├) => Some(├)
+ let boundary_type = boundary_type::VERTICAL_RIGHT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_LEFT, boundary_type::HORIZONTAL_DOWN) => {
+ // (┌, ┬) => Some(┬)
+ let boundary_type = boundary_type::HORIZONTAL_DOWN;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_LEFT, boundary_type::HORIZONTAL_UP) => {
+ // (┌, ┴) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::TOP_LEFT, boundary_type::CROSS) => {
+ // (┌, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_RIGHT, boundary_type::BOTTOM_RIGHT) => {
+ // (┘, ┘) => Some(┘)
+ let boundary_type = boundary_type::BOTTOM_RIGHT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_RIGHT, boundary_type::BOTTOM_LEFT) => {
+ // (┘, └) => Some(┴)
+ let boundary_type = boundary_type::HORIZONTAL_UP;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_RIGHT, boundary_type::VERTICAL_LEFT) => {
+ // (┘, ┤) => Some(┤)
+ let boundary_type = boundary_type::VERTICAL_LEFT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_RIGHT, boundary_type::VERTICAL_RIGHT) => {
+ // (┘, ├) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_RIGHT, boundary_type::HORIZONTAL_DOWN) => {
+ // (┘, ┬) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_RIGHT, boundary_type::HORIZONTAL_UP) => {
+ // (┘, ┴) => Some(┴)
+ let boundary_type = boundary_type::HORIZONTAL_UP;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_RIGHT, boundary_type::CROSS) => {
+ // (┘, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_LEFT, boundary_type::BOTTOM_LEFT) => {
+ // (└, └) => Some(└)
+ let boundary_type = boundary_type::BOTTOM_LEFT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_LEFT, boundary_type::VERTICAL_LEFT) => {
+ // (└, ┤) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_LEFT, boundary_type::VERTICAL_RIGHT) => {
+ // (└, ├) => Some(├)
+ let boundary_type = boundary_type::VERTICAL_RIGHT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_LEFT, boundary_type::HORIZONTAL_DOWN) => {
+ // (└, ┬) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_LEFT, boundary_type::HORIZONTAL_UP) => {
+ // (└, ┴) => Some(┴)
+ let boundary_type = boundary_type::HORIZONTAL_UP;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::BOTTOM_LEFT, boundary_type::CROSS) => {
+ // (└, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL_LEFT, boundary_type::VERTICAL_LEFT) => {
+ // (┤, ┤) => Some(┤)
+ let boundary_type = boundary_type::VERTICAL_LEFT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL_LEFT, boundary_type::VERTICAL_RIGHT) => {
+ // (┤, ├) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL_LEFT, boundary_type::HORIZONTAL_DOWN) => {
+ // (┤, ┬) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL_LEFT, boundary_type::HORIZONTAL_UP) => {
+ // (┤, ┴) => Some(┼)
+ let boundary_type = boundary_type::HORIZONTAL_UP;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL_LEFT, boundary_type::CROSS) => {
+ // (┤, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL_RIGHT, boundary_type::VERTICAL_RIGHT) => {
+ // (├, ├) => Some(├)
+ let boundary_type = boundary_type::VERTICAL_RIGHT;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL_RIGHT, boundary_type::HORIZONTAL_DOWN) => {
+ // (├, ┬) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL_RIGHT, boundary_type::HORIZONTAL_UP) => {
+ // (├, ┴) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::VERTICAL_RIGHT, boundary_type::CROSS) => {
+ // (├, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL_DOWN, boundary_type::HORIZONTAL_DOWN) => {
+ // (┬, ┬) => Some(┬)
+ let boundary_type = boundary_type::HORIZONTAL_DOWN;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL_DOWN, boundary_type::HORIZONTAL_UP) => {
+ // (┬, ┴) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL_DOWN, boundary_type::CROSS) => {
+ // (┬, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL_UP, boundary_type::HORIZONTAL_UP) => {
+ // (┴, ┴) => Some(┴)
+ let boundary_type = boundary_type::HORIZONTAL_UP;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::HORIZONTAL_UP, boundary_type::CROSS) => {
+ // (┴, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (boundary_type::CROSS, boundary_type::CROSS) => {
+ // (┼, ┼) => Some(┼)
+ let boundary_type = boundary_type::CROSS;
+ Some(BoundarySymbol {
+ boundary_type,
+ invisible,
+ })
+ }
+ (_, _) => None,
+ }
+}
+
+fn find_next_symbol(
+ first_symbol: BoundarySymbol,
+ second_symbol: BoundarySymbol,
+) -> Option<BoundarySymbol> {
+ if let Some(symbol) = combine_symbols(first_symbol, second_symbol) {
+ Some(symbol)
+ } else {
+ combine_symbols(second_symbol, first_symbol)
+ }
+}
+
+#[derive(PartialEq, Eq, Hash, Debug)]
+pub struct Coordinates {
+ x: usize,
+ y: usize,
+}
+
+impl Coordinates {
+ pub fn new(x: usize, y: usize) -> Self {
+ Coordinates { x, y }
+ }
+}
+
+pub trait Rect {
+ fn x(&self) -> usize;
+ fn y(&self) -> usize;
+ fn rows(&self) -> usize;
+ fn columns(&self) -> usize;
+ fn right_boundary_x_coords(&self) -> usize {
+ self.x() + self.columns()
+ }
+ fn bottom_boundary_y_coords(&self) -> usize {
+ self.y() + self.rows()
+ }
+ fn is_directly_right_of(&self, other: &Self) -> bool {
+ self.x() == other.x() + other.columns() + 1
+ }
+ fn is_directly_left_of(&self, other: &Self) -> bool {
+ self.x() + self.columns() + 1 == other.x()
+ }
+ fn is_directly_below(&self, other: &Self) -> bool {
+ self.y() == other.y() + other.rows() + 1
+ }
+ fn is_directly_above(&self, other: &Self) -> bool {
+ self.y() + self.rows() + 1 == other.y()
+ }
+ fn horizontally_overlaps_with(&self, other: &Self) -> bool {
+ (self.y() >= other.y() && self.y() <= (other.y() + other.rows()))
+ || ((self.y() + self.rows()) <= (other.y() + other.rows())
+ && (self.y() + self.rows()) > other.y())
+ || (self.y() <= other.y() && (self.y() + self.rows() >= (other.y() + other.rows())))
+ || (other.y() <= self.y() && (other.y() + other.rows() >= (self.y() + self.rows())))
+ }
+ fn get_horizontal_overlap_with(&self, other: &Self) -> usize {
+ std::cmp::min(self.y() + self.rows(), other.y() + other.rows())
+ - std::cmp::max(self.y(), other.y())
+ }
+ fn vertically_overlaps_with(&self, other: &Self) -> bool {
+ (self.x() >= other.x() && self.x() <= (other.x() + other.columns()))
+ || ((self.x() + self.columns()) <= (other.x() + other.columns())
+ && (self.x() + self.columns()) > other.x())
+ || (self.x() <= other.x()
+ && (self.x() + self.columns() >= (other.x() + other.columns())))
+ || (other.x() <= self.x()
+ && (other.x() + other.columns() >= (self.x() + self.columns())))
+ }
+ fn get_vertical_overlap_with(&self, other: &Self) -> usize {
+ std::cmp::min(self.x() + self.columns(), other.x() + other.columns())
+ - std::cmp::max(self.x(), other.x())
+ }
+}
+
+pub struct Boundaries {
+ columns: usize,
+ rows: usize,
+ // boundary_characters: HashMap<Coordinates, BoundaryType>,
+ boundary_characters: HashMap<Coordinates, BoundarySymbol>,
+}
+
+impl Boundaries {
+ pub fn new(columns: u16, rows: u16) -> Self {
+ let columns = columns as usize;
+ let rows = rows as usize;
+ Boundaries {
+ columns,
+ rows,
+ boundary_characters: HashMap::new(),
+ }
+ }
+ pub fn add_rect(&mut self, rect: &dyn Pane) {
+ if rect.x() > 0 {
+ let boundary_x_coords = rect.x() - 1;
+ let first_row_coordinates = self.rect_right_boundary_row_start(rect);
+ let last_row_coordinates = self.rect_right_boundary_row_end(rect);
+ for row in first_row_coordinates..last_row_coordinates {
+ let coordinates = Coordinates::new(boundary_x_coords, row);
+ let mut symbol_to_add = if row == first_row_coordinates && row != 0 {
+ BoundarySymbol::new(boundary_type::TOP_LEFT)
+ } else if row == last_row_coordinates - 1 && row != self.rows - 1 {
+ BoundarySymbol::new(boundary_type::BOTTOM_LEFT)
+ } else {
+ BoundarySymbol::new(boundary_type::VERTICAL)
+ };
+ if rect.invisible_borders() {
+ symbol_to_add = symbol_to_add.invisible();
+ }
+ let next_symbol = self
+ .boundary_characters
+ .remove(&coordinates)
+ .and_then(|current_symbol| find_next_symbol(current_symbol, symbol_to_add))
+ .unwrap_or(symbol_to_add);
+ self.boundary_characters.insert(coordinates, next_symbol);
+ }
+ }
+ if rect.y() > 0 {
+ let boundary_y_coords = rect.y() - 1;
+ let first_col_coordinates = self.rect_bottom_boundary_col_start(rect);
+ let last_col_coordinates = self.rect_bottom_boundary_col_end(rect);
+ for col in first_col_coordinates..last_col_coordinates {
+ let coordinates = Coordinates::new(col, boundary_y_coords);
+ let mut symbol_to_add = if col == first_col_coordinates && col != 0 {
+ BoundarySymbol::new(boundary_type::TOP_LEFT)
+ } else if col == last_col_coordinates - 1 && col != self.columns - 1 {
+ BoundarySymbol::new(boundary_type::TOP_RIGHT)
+ } else {
+ BoundarySymbol::new(boundary_type::HORIZONTAL)
+ };
+ if rect.invisible_borders() {
+ symbol_to_add = symbol_to_add.invisible();
+ }
+ let next_symbol = self
+ .boundary_characters
+ .remove(&coordinates)
+ .and_then(|current_symbol| find_next_symbol(current_symbol, symbol_to_add))
+ .unwrap_or(symbol_to_add);
+ self.boundary_characters.insert(coordinates, next_symbol);
+ }
+ }
+ if self.rect_right_boundary_is_before_screen_edge(rect) {
+ // let boundary_x_coords = self.rect_right_boundary_x_coords(rect);
+ let boundary_x_coords = rect.right_boundary_x_coords();
+ let first_row_coordinates = self.rect_right_boundary_row_start(rect);
+ let last_row_coordinates = self.rect_right_boundary_row_end(rect);
+ for row in first_row_coordinates..last_row_coordinates {
+ let coordinates = Coordinates::new(boundary_x_coords, row);
+ let mut symbol_to_add = if row == first_row_coordinates && row != 0 {
+ BoundarySymbol::new(boundary_type::TOP_RIGHT)
+ } else if row == last_row_coordinates - 1 && row != self.rows - 1 {
+ BoundarySymbol::new(boundary_type::BOTTOM_RIGHT)
+ } else {
+ BoundarySymbol::new(boundary_type::VERTICAL)
+ };
+ if rect.invisible_borders() {
+ symbol_to_add = symbol_to_add.invisible();
+ }
+ let next_symbol = self
+ .boundary_characters
+ .remove(&coordinates)
+ .and_then(|current_symbol| find_next_symbol(current_symbol, symbol_to_add))
+ .unwrap_or(symbol_to_add);
+ self.boundary_characters.insert(coordinates, next_symbol);
+ }
+ }
+ if self.rect_bottom_boundary_is_before_screen_edge(rect) {
+ let boundary_y_coords = rect.bottom_boundary_y_coords();
+ let first_col_coordinates = self.rect_bottom_boundary_col_start(rect);
+ let last_col_coordinates = self.rect_bottom_boundary_col_end(rect);
+ for col in first_col_coordinates..last_col_coordinates {
+ let coordinates = Coordinates::new(col, boundary_y_coords);
+ let mut symbol_to_add = if col == first_col_coordinates && col != 0 {
+ BoundarySymbol::new(boundary_type::BOTTOM_LEFT)
+ } else if col == last_col_coordinates - 1 && col != self.columns - 1 {
+ BoundarySymbol::new(boundary_type::BOTTOM_RIGHT)
+ } else {
+ BoundarySymbol::new(boundary_type::HORIZONTAL)
+ };
+ if rect.invisible_borders() {
+ symbol_to_add = symbol_to_add.invisible();
+ }
+ let next_symbol = self
+ .boundary_characters
+ .remove(&coordinates)
+ .and_then(|current_symbol| find_next_symbol(current_symbol, symbol_to_add))
+ .unwrap_or(symbol_to_add);
+ self.boundary_characters.insert(coordinates, next_symbol);
+ }
+ }
+ }
+ pub fn vte_output(&self) -> String {
+ let mut vte_output = String::new();
+ for (coordinates, boundary_character) in &self.boundary_characters {
+ vte_output.push_str(&format!(
+ "\u{1b}[{};{}H\u{1b}[m{}",
+ coordinates.y + 1,
+ coordinates.x + 1,
+ boundary_character
+ )); // goto row/col + boundary character
+ }
+ vte_output
+ }
+ fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
+ rect.x() + rect.columns() < self.columns
+ }
+ fn rect_bottom_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
+ rect.y() + rect.rows() < self.rows
+ }
+ fn rect_right_boundary_row_start(&self, rect: &dyn Pane) -> usize {
+ if rect.y() == 0 {
+ 0
+ } else {
+ rect.y() - 1
+ }
+ }
+ fn rect_right_boundary_row_end(&self, rect: &dyn Pane) -> usize {
+ let rect_bottom_row = rect.y() + rect.rows();
+ // we do this because unless we're on the screen edge, we'd like to go one extra row to
+ // connect to whatever boundary is beneath us
+ if rect_bottom_row == self.rows {
+ rect_bottom_row
+ } else {
+ rect_bottom_row + 1
+ }
+ }
+ fn rect_bottom_boundary_col_start(&self, rect: &dyn Pane) -> usize {
+ if rect.x() == 0 {
+ 0
+ } else {
+ rect.x() - 1
+ }
+ }
+ fn rect_bottom_boundary_col_end(&self, rect: &dyn Pane) -> usize {
+ let rect_right_col = rect.x() + rect.columns();
+ // we do this because unless we're on the screen edge, we'd like to go one extra column to
+ // connect to whatever boundary is right of us
+ if rect_right_col == self.columns {
+ rect_right_col
+ } else {
+ rect_right_col + 1
+ }
+ }
+}
diff --git a/src/client/layout.rs b/src/client/layout.rs
new file mode 100644
index 00000000..d09e7618
--- /dev/null
+++ b/src/client/layout.rs
@@ -0,0 +1,220 @@
+use crate::utils::consts::MOSAIC_ROOT_LAYOUT_DIR;
+use directories_next::ProjectDirs;
+use serde::{Deserialize, Serialize};
+use std::path::{Path, PathBuf};
+use std::{fs::File, io::prelude::*};
+
+use crate::panes::PositionAndSize;
+
+fn split_space_to_parts_vertically(
+ space_to_split: &PositionAndSize,
+ sizes: Vec<Option<SplitSize>>,
+) -> Vec<PositionAndSize> {
+ let mut split_parts = Vec::new();
+ let mut current_x_position = space_to_split.x;
+ let mut current_width = 0;
+ let max_width = space_to_split.columns - (sizes.len() - 1); // minus space for gaps
+
+ let mut parts_to_grow = Vec::new();
+
+ // First fit in the parameterized sizes
+ for size in sizes {
+ let columns = match size {
+ Some(SplitSize::Percent(percent)) => {
+ (max_width as f32 * (percent as f32 / 100.0)) as usize
+ } // TODO: round properly
+ Some(SplitSize::Fixed(size)) => size as usize,
+ None => {
+ parts_to_grow.push(current_x_position);
+ 1 // This is grown later on
+ }
+ };
+ split_parts.push(PositionAndSize {
+ x: current_x_position,
+ y: space_to_split.y,
+ columns,
+ rows: space_to_split.rows,
+ });
+ current_width += columns;
+ current_x_position += columns + 1; // 1 for gap
+ }
+
+ if current_width > max_width {
+ panic!("Layout contained too many columns to fit onto the screen!");
+ }
+
+ let mut last_flexible_index = split_parts.len() - 1;
+ if let Some(new_columns) = (max_width - current_width).checked_div(parts_to_grow.len()) {
+ current_width = 0;
+ current_x_position = 0;
+ for (idx, part) in split_parts.iter_mut().enumerate() {
+ part.x = current_x_position;
+ if parts_to_grow.contains(&part.x) {
+ part.columns = new_columns;
+ last_flexible_index = idx;
+ }
+ current_width += part.columns;
+ current_x_position += part.columns + 1; // 1 for gap
+ }
+ }
+
+ if current_width < max_width {
+ // we have some extra space left, let's add it to the last flexible part
+ let extra = max_width - current_width;
+ let mut last_part = split_parts.get_mut(last_flexible_index).unwrap();
+ last_part.columns += extra;
+ for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() {
+ part.x += extra;
+ }
+ }
+ split_parts
+}
+
+fn split_space_to_parts_horizontally(
+ space_to_split: &PositionAndSize,
+ sizes: Vec<Option<SplitSize>>,
+) -> Vec<PositionAndSize> {
+ let mut split_parts = Vec::new();
+ let mut current_y_position = space_to_split.y;
+ let mut current_height = 0;
+ let max_height = space_to_split.rows - (sizes.len() - 1); // minus space for gaps
+
+ let mut parts_to_grow = Vec::new();
+
+ for size in sizes {
+ let rows = match size {
+ Some(SplitSize::Percent(percent)) => {
+ (max_height as f32 * (percent as f32 / 100.0)) as usize
+ } // TODO: round properly
+ Some(SplitSize::Fixed(size)) => size as usize,
+ None => {
+ parts_to_grow.push(current_y_position);
+ 1 // This is grown later on
+ }
+ };
+ split_parts.push(PositionAndSize {
+ x: space_to_split.x,
+ y: current_y_position,
+ columns: space_to_split.columns,
+ rows,
+ });
+ current_height += rows;
+ current_y_position += rows + 1; // 1 for gap
+ }
+
+ if current_height > max_height {
+ panic!("Layout contained too many rows to fit onto the screen!");
+ }
+
+ let mut last_flexible_index = split_parts.len() - 1;
+ if let Some(new_rows) = (max_height - current_height).checked_div(parts_to_grow.len()) {
+ current_height = 0;
+ current_y_position = 0;
+
+ for (idx, part) in split_parts.iter_mut().enumerate() {
+ part.y = current_y_position;
+ if parts_to_grow.contains(&part.y) {
+ part.rows = new_rows;
+ last_flexible_index = idx;
+ }
+ current_height += part.rows;
+ current_y_position += part.rows + 1; // 1 for gap
+ }
+ }
+
+ if current_height < max_height {
+ // we have some extra space left, let's add it to the last flexible part
+ let extra = max_height - current_height;
+ let mut last_part = split_parts.get_mut(last_flexible_index).unwrap();
+ last_part.rows += extra;
+ for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() {
+ part.y += extra;
+ }
+ }
+ split_parts
+}
+
+fn split_space(
+ space_to_split: &PositionAndSize,
+ layout: &Layout,
+) -> Vec<(Layout, PositionAndSize)> {
+ let mut pane_positions = Vec::new();
+ let sizes: Vec<Option<SplitSize>> = layout.parts.iter().map(|part| part.split_size).collect();
+
+ let split_parts = match layout.direction {
+ Direction::Vertical => split_space_to_parts_vertically(space_to_split, sizes),
+ Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, sizes),
+ };
+ for (i, part) in layout.parts.iter().enumerate() {
+ let part_position_and_size = split_parts.get(i).unwrap();
+ if !part.parts.is_empty() {
+ let mut part_positions = split_space(&part_position_and_size, part);
+ pane_positions.append(&mut part_positions);
+ } else {
+ pane_positions.push((part.clone(), *part_position_and_size));
+ }
+ }
+ pane_positions
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub enum Direction {
+ Horizontal,
+ Vertical,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
+pub enum SplitSize {
+ Percent(u8), // 1 to 100
+ Fixed(u16), // An absolute number of columns or rows
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct Layout {
+ pub direction: Direction,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub parts: Vec<Layout>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub split_size: Option<SplitSize>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub plugin: Option<PathBuf>,
+}
+
+impl Layout {
+ pub fn new(layout_path: PathBuf) -> Self {
+ let project_dirs = ProjectDirs::from("org", "Mosaic Contributors", "Mosaic").unwrap();
+ let layout_dir = project_dirs.data_dir().join("layouts/");
+ let root_layout_dir = Path::new(MOSAIC_ROOT_LAYOUT_DIR);
+ let mut layout_file = File::open(&layout_path)
+ .or_else(|_| File::open(&layout_path.with_extension("yaml")))
+ .or_else(|_| File::open(&layout_dir.join(&layout_path).with_extension("yaml")))
+ .or_else(|_| File::open(root_layout_dir.join(&layout_path).with_extension("yaml")))
+ .unwrap_or_else(|_| panic!("cannot find layout {}", &layout_path.display()));
+
+ let mut layout = String::new();
+ layout_file
+ .read_to_string(&mut layout)
+ .unwrap_or_else(|_| panic!("could not read layout {}", &layout_path.display()));
+ let layout: Layout = serde_yaml::from_str(&layout)
+ .unwrap_or_else(|_| panic!("could not parse layout {}", &layout_path.display()));
+ layout
+ }
+
+ pub fn total_terminal_panes(&self) -> usize {
+ let mut total_panes = 0;
+ total_panes += self.parts.len();
+ for part in self.parts.iter() {
+ if part.plugin.is_none() {
+ total_panes += part.total_terminal_panes();
+ }
+ }
+ total_panes
+ }
+
+ pub fn position_panes_in_space(
+ &self,
+ space: &PositionAndSize,
+ ) -> Vec<(Layout, PositionAndSize)> {
+ split_space(space, &self)
+ }
+}
diff --git a/src/client/mod.rs b/src/client/mod.rs
new file mode 100644
index 00000000..c8276253
--- /dev/null
+++ b/src/client/mod.rs
@@ -0,0 +1,6 @@
+pub mod boundaries;
+pub mod layout;
+pub mod panes;
+pub mod tab;
+
+pub fn start_client() {}
diff --git a/src/client/panes/grid.rs b/src/client/panes/grid.rs
new file mode 100644
index 00000000..062b5c3a
--- /dev/null
+++ b/src/client/panes/grid.rs
@@ -0,0 +1,820 @@
+use std::{
+ cmp::Ordering,
+ fmt::{self, Debug, Formatter},
+};
+
+use crate::panes::terminal_character::{
+ CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
+};
+
+fn get_top_non_canonical_rows(rows: &mut Vec<Row>) -> Vec<Row> {
+ let mut index_of_last_non_canonical_row = None;
+ for (i, row) in rows.iter().enumerate() {
+ if row.is_canonical {
+ break;
+ } else {
+ index_of_last_non_canonical_row = Some(i);
+ }
+ }
+ match index_of_last_non_canonical_row {
+ Some(index_of_last_non_canonical_row) => {
+ rows.drain(..=index_of_last_non_canonical_row).collect()
+ }
+ None => vec![],
+ }
+}
+
+fn get_bottom_canonical_row_and_wraps(rows: &mut Vec<Row>) -> Vec<Row> {
+ let mut index_of_last_non_canonical_row = None;
+ for (i, row) in rows.iter().enumerate().rev() {
+ if row.is_canonical {
+ index_of_last_non_canonical_row = Some(i);
+ break;
+ } else {
+ index_of_last_non_canonical_row = Some(i);
+ }
+ }
+ match index_of_last_non_canonical_row {
+ Some(index_of_last_non_canonical_row) => {
+ rows.drain(index_of_last_non_canonical_row..).collect()
+ }
+ None => vec![],
+ }
+}
+
+fn transfer_rows_down(
+ source: &mut Vec<Row>,
+ destination: &mut Vec<Row>,
+ count: usize,
+ max_src_width: Option<usize>,
+ max_dst_width: Option<usize>,
+) {
+ let mut next_lines: Vec<Row> = vec![];
+ let mut lines_added_to_destination: isize = 0;
+ loop {
+ if lines_added_to_destination as usize == count {
+ break;
+ }
+ if next_lines.is_empty() {
+ match source.pop() {
+ Some(next_line) => {
+ let mut top_non_canonical_rows_in_dst = get_top_non_canonical_rows(destination);
+ lines_added_to_destination -= top_non_canonical_rows_in_dst.len() as isize;
+ next_lines.push(next_line);
+ next_lines.append(&mut top_non_canonical_rows_in_dst);
+ next_lines = match max_dst_width {
+ Some(max_row_width) => {
+ Row::from_rows(next_lines).split_to_rows_of_length(max_row_width)
+ }
+ None => vec![Row::from_rows(next_lines)],
+ };
+ }
+ None => break, // no more rows
+ }
+ }
+ destination.insert(0, next_lines.pop().unwrap());
+ lines_added_to_destination += 1;
+ }
+ if !next_lines.is_empty() {
+ match max_src_width {
+ Some(max_row_width) => {
+ let mut excess_rows =
+ Row::from_rows(next_lines).split_to_rows_of_length(max_row_width);
+ source.append(&mut excess_rows);
+ }
+ None => {
+ let excess_row = Row::from_rows(next_lines);
+ source.push(excess_row);
+ }
+ }
+ }
+}
+
+fn transfer_rows_up(
+ source: &mut Vec<Row>,
+ destination: &mut Vec<Row>,
+ count: usize,
+ max_src_width: Option<usize>,
+ max_dst_width: Option<usize>,
+) {
+ let mut next_lines: Vec<Row> = vec![];
+ for _ in 0..count {
+ if next_lines.is_empty() {
+ if !source.is_empty() {
+ let next_line = source.remove(0);
+ if !next_line.is_canonical {
+ let mut bottom_canonical_row_and_wraps_in_dst =
+ get_bottom_canonical_row_and_wraps(destination);
+ next_lines.append(&mut bottom_canonical_row_and_wraps_in_dst);
+ }
+ next_lines.push(next_line);
+ next_lines = match max_dst_width {
+ Some(max_row_width) => {
+ Row::from_rows(next_lines).split_to_rows_of_length(max_row_width)
+ }
+ None => vec![Row::from_rows(next_lines)],
+ };
+ } else {
+ break; // no more rows
+ }
+ }
+ destination.push(next_lines.remove(0));
+ }
+ if !next_lines.is_empty() {
+ match max_src_width {
+ Some(max_row_width) => {
+ let excess_rows = Row::from_rows(next_lines).split_to_rows_of_length(max_row_width);
+ for row in excess_rows {
+ source.insert(0, row);
+ }
+ }
+ None => {
+ let excess_row = Row::from_rows(next_lines);
+ source.insert(0, excess_row);
+ }
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct Grid {
+ lines_above: Vec<Row>,
+ viewport: Vec<Row>,
+ lines_below: Vec<Row>,
+ cursor: Cursor,
+ scroll_region: Option<(usize, usize)>,
+ pub width: usize,
+ pub height: usize,
+}
+
+impl Debug for Grid {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ for (i, row) in self.viewport.iter().enumerate() {
+ if row.is_canonical {
+ writeln!(f, "{:?} (C): {:?}", i, row)?;
+ } else {
+ writeln!(f, "{:?} (W): {:?}", i, row)?;
+ }
+ }
+ Ok(())
+ }
+}
+
+impl Grid {
+ pub fn new(rows: usize, columns: usize) -> Self {
+ Grid {
+ lines_above: vec![],
+ viewport: vec![],
+ lines_below: vec![],
+ cursor: Cursor::new(0, 0),
+ scroll_region: None,
+ width: columns,
+ height: rows,
+ }
+ }
+ fn cursor_canonical_line_index(&self) -> usize {
+ let mut cursor_canonical_line_index = 0;
+ let mut canonical_lines_traversed = 0;
+ for (i, line) in self.viewport.iter().enumerate() {
+ if line.is_canonical {
+ cursor_canonical_line_index = canonical_lines_traversed;
+ canonical_lines_traversed += 1;
+ }
+ if i == self.cursor.y {
+ break;
+ }
+ }
+ cursor_canonical_line_index
+ }
+ // TODO: merge these two funtions
+ fn cursor_index_in_canonical_line(&self) -> usize {
+ let mut cursor_canonical_line_index = 0;
+ let mut cursor_index_in_canonical_line = 0;
+ for (i, line) in self.viewport.iter().enumerate() {
+ if line.is_canonical {
+ cursor_canonical_line_index = i;
+ }
+ if i == self.cursor.y {
+ let line_wrap_position_in_line = self.cursor.y - cursor_canonical_line_index;
+ cursor_index_in_canonical_line = line_wrap_position_in_line + self.cursor.x;
+ break;
+ }
+ }
+ cursor_index_in_canonical_line
+ }
+ fn canonical_line_y_coordinates(&self, canonical_line_index: usize) -> usize {
+ let mut canonical_lines_traversed = 0;
+ let mut y_coordinates = 0;
+ for (i, line) in self.viewport.iter().enumerate() {
+ if line.is_canonical {
+ canonical_lines_traversed += 1;
+ if canonical_lines_traversed == canonical_line_index + 1 {
+ y_coordinates = i;
+ break;
+ }
+ }
+ }
+ y_coordinates
+ }
+ pub fn scroll_up_one_line(&mut self) {
+ if !self.lines_above.is_empty() && self.viewport.len() == self.height {
+ let line_to_push_down = self.viewport.pop().unwrap();
+ self.lines_below.insert(0, line_to_push_down);
+ let line_to_insert_at_viewport_top = self.lines_above.pop().unwrap();
+ self.viewport.insert(0, line_to_insert_at_viewport_top);
+ }
+ }
+ pub fn scroll_down_one_line(&mut self) {
+ if !self.lines_below.is_empty() && self.viewport.len() == self.height {
+ let mut line_to_push_up = self.viewport.remove(0);
+ if line_to_push_up.is_canonical {
+ self.lines_above.push(line_to_push_up);
+ } else {
+ let mut last_line_above = self.lines_above.pop().unwrap();
+ last_line_above.append(&mut line_to_push_up.columns);
+ self.lines_above.push(last_line_above);
+ }
+ let line_to_insert_at_viewport_bottom = self.lines_below.remove(0);
+ self.viewport.push(line_to_insert_at_viewport_bottom);
+ }
+ }
+ pub fn change_size(&mut self, new_rows: usize, new_columns: usize) {
+ if new_columns != self.width {
+ let mut cursor_canonical_line_index = self.cursor_canonical_line_index();
+ let cursor_index_in_canonical_line = self.cursor_index_in_canonical_line();
+ let mut viewport_canonical_lines = vec![];
+ for mut row in self.viewport.drain(..) {
+ if !row.is_canonical
+ && viewport_canonical_lines.is_empty()
+ && !self.lines_above.is_empty()
+ {
+ let mut first_line_above = self.lines_above.pop().unwrap();
+ first_line_above.append(&mut row.columns);
+ viewport_canonical_lines.push(first_line_above);
+ cursor_canonical_line_index += 1;
+ } else if row.is_canonical {
+ viewport_canonical_lines.push(row);
+ } else {
+ match viewport_canonical_lines.last_mut() {
+ Some(last_line) => {
+ last_line.append(&mut row.columns);
+ }
+ None => {
+ // the state is corrupted somehow
+ // this is a bug and I'm not yet sure why it happens
+ // usually it fixes itself and is a result of some race
+ // TODO: investigate why this happens and solve it
+ return;
+ }
+ }
+ }
+ }
+ let mut new_viewport_rows = vec![];
+ for mut canonical_line in viewport_canonical_lines {
+ let mut canonical_line_parts: Vec<Row> = vec![];
+ if canonical_line.columns.is_empty() {
+ canonical_line_parts.push(Row::new().canonical());
+ }
+ while !canonical_line.columns.is_empty() {
+ let next_wrap = if canonical_line.len() > new_columns {
+ canonical_line.columns.drain(..new_columns)
+ } else {
+ canonical_line.columns.drain(..)
+ };
+ let row = Row::from_columns(next_wrap.collect());
+ // if there are no more parts, this row is canonical as long as it originally
+ // was canonical (it might not have been for example if it's the first row in
+ // the viewport, and the actual canonical row is above it in the scrollback)
+ let row = if canonical_line_parts.is_empty() && canonical_line.is_canonical {
+ row.canonical()
+ } else {
+ row
+ };
+ canonical_line_parts.push(row);
+ }
+ new_viewport_rows.append(&mut canonical_line_parts);
+ }
+ self.viewport = new_viewport_rows;
+
+ let mut new_cursor_y = self.canonical_line_y_coordinates(cursor_canonical_line_index);
+ let new_cursor_x = (cursor_index_in_canonical_line / new_columns)
+ + (cursor_index_in_canonical_line % new_columns);
+ let current_viewport_row_count = self.viewport.len();
+ match current_viewport_row_count.cmp(&self.height) {
+ Ordering::Less => {
+ let row_count_to_transfer = self.height - current_viewport_row_count;
+ transfer_rows_down(
+ &mut self.lines_above,
+ &mut self.viewport,
+ row_count_to_transfer,
+ None,
+ Some(new_columns),
+ );
+ let rows_pulled = self.viewport.len() - current_viewport_row_count;
+ new_cursor_y += rows_pulled;
+ }
+ Ordering::Greater => {
+ let row_count_to_transfer = current_viewport_row_count - self.height;
+ if row_count_to_transfer > new_cursor_y {
+ new_cursor_y = 0;
+ } else {
+ new_cursor_y -= row_count_to_transfer;
+ }
+ transfer_rows_up(
+ &mut self.viewport,
+ &mut self.lines_above,
+ row_count_to_transfer,
+ Some(new_columns),
+ None,
+ );
+ }
+ Ordering::Equal => {}
+ }
+ self.cursor.y = new_cursor_y;
+ self.cursor.x = new_cursor_x;
+ }
+ if new_rows != self.height {
+ let current_viewport_row_count = self.viewport.len();
+ match current_viewport_row_count.cmp(&new_rows) {
+ Ordering::Less => {
+ let row_count_to_transfer = new_rows - current_viewport_row_count;
+ transfer_rows_down(
+ &mut self.lines_above,
+ &mut self.viewport,
+ row_count_to_transfer,
+ None,
+ Some(new_columns),
+ );
+ let rows_pulled = self.viewport.len() - current_viewport_row_count;
+ self.cursor.y += rows_pulled;
+ }
+ Ordering::Greater => {
+ let row_count_to_transfer = current_viewport_row_count - new_rows;
+ if row_count_to_transfer > self.cursor.y {
+ self.cursor.y = 0;
+ } else {
+ self.cursor.y -= row_count_to_transfer;
+ }
+ transfer_rows_up(
+ &mut self.viewport,
+ &mut self.lines_above,
+ row_count_to_transfer,
+ Some(new_columns),
+ None,
+ );
+ }
+ Ordering::Equal => {}
+ }
+ }
+ self.height = new_rows;
+ self.width = new_columns;
+ if self.scroll_region.is_some() {
+ self.set_scroll_region_to_viewport_size();
+ }
+ }
+ pub fn as_character_lines(&self) -> Vec<Vec<TerminalCharacter>> {
+ let mut lines: Vec<Vec<TerminalCharacter>> = self
+ .viewport
+ .iter()
+ .map(|r| {
+ let mut line: Vec<TerminalCharacter> = r.columns.iter().copied().collect();
+ // pad line
+ line.resize(self.width, EMPTY_TERMINAL_CHARACTER);
+ line
+ })
+ .collect();
+ let empty_row = vec![EMPTY_TERMINAL_CHARACTER; self.width];
+ for _ in lines.len()..self.height {
+ lines.push(empty_row.clone());
+ }
+ lines
+ }
+ pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
+ if self.cursor.is_hidden {
+ None
+ } else {
+ Some((self.cursor.x, self.cursor.y))
+ }
+ }
+ pub fn move_viewport_up(&mut self, count: usize) {
+ for _ in 0..count {
+ self.scroll_up_one_line();
+ }
+ }
+ pub fn move_viewport_down(&mut self, count: usize) {
+ for _ in 0..count {
+ self.scroll_down_one_line();
+ }
+ }
+ pub fn reset_viewport(&mut self) {
+ let row_count_below = self.lines_below.len();
+ for _ in 0..row_count_below {
+ self.scroll_down_one_line();
+ }
+ }
+ pub fn rotate_scroll_region_up(&mut self, _count: usize) {
+ // TBD
+ }
+ pub fn rotate_scroll_region_down(&mut self, _count: usize) {
+ // TBD
+ }
+ pub fn add_canonical_line(&mut self) {
+ if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
+ if self.cursor.y == scroll_region_bottom {
+ // end of scroll region
+ // when we have a scroll region set and we're at its bottom
+ // we need to delete its first line, thus shifting all lines in it upwards
+ // then we add an empty line at its end which will be filled by the application
+ // controlling the scroll region (presumably filled by whatever comes next in the
+ // scroll buffer, but that's not something we control)
+ self.viewport.remove(scroll_region_top);
+ self.viewport
+ .insert(scroll_region_bottom, Row::new().canonical());
+ return;
+ }
+ }
+ if self.viewport.len() <= self.cursor.y + 1 {
+ let new_row = Row::new().canonical();
+ self.viewport.push(new_row);
+ }
+ if self.cursor.y == self.height - 1 {
+ let row_count_to_transfer = 1;
+ transfer_rows_up(
+ &mut self.viewport,
+ &mut self.lines_above,
+ row_count_to_transfer,
+ Some(self.width),
+ None,
+ );
+ } else {
+ self.cursor.y += 1;
+ }
+ }
+ pub fn move_cursor_to_beginning_of_line(&mut self) {
+ self.cursor.x = 0;
+ }
+ pub fn move_cursor_backwards(&mut self, count: usize) {
+ if self.cursor.x > count {
+ self.cursor.x -= count;
+ } else {
+ self.cursor.x = 0;
+ }
+ }
+ pub fn insert_character_at_cursor_position(&mut self, terminal_character: TerminalCharacter) {
+ match self.viewport.get_mut(self.cursor.y) {
+ Some(row) => row.add_character_at(terminal_character, self.cursor.x),
+ None => {
+ // pad lines until cursor if they do not exist
+ for _ in self.viewport.len()..self.cursor.y {
+ self.viewport.push(Row::new().canonical());
+ }
+ self.viewport
+ .push(Row::new().with_character(terminal_character).canonical());
+ }
+ }
+ }
+ pub fn add_character(&mut self, terminal_character: TerminalCharacter) {
+ // TODO: try to separate adding characters from moving the cursors in this function
+ if self.cursor.x < self.width {
+ self.insert_character_at_cursor_position(terminal_character);
+ } else {
+ // line wrap
+ self.cursor.x = 0;
+ if self.cursor.y == self.height - 1 {
+ let row_count_to_transfer = 1;
+ transfer_rows_up(
+ &mut self.viewport,
+ &mut self.lines_above,
+ row_count_to_transfer,
+ Some(self.width),
+ None,
+ );
+ let wrapped_row = Row::new();
+ self.viewport.push(wrapped_row);
+ } else {
+ self.cursor.y += 1;
+ if self.viewport.len() <= self.cursor.y {
+ let line_wrapped_row = Row::new();
+ self.viewport.push(line_wrapped_row);
+ }
+ }
+ self.insert_character_at_cursor_position(terminal_character);
+ }
+ self.move_cursor_forward_until_edge(1);
+ }
+ pub fn move_cursor_forward_until_edge(&mut self, count: usize) {
+ let count_to_move = std::cmp::min(count, self.width - (self.cursor.x));
+ self.cursor.x += count_to_move;
+ }
+ pub fn replace_characters_in_line_after_cursor(&mut self, replace_with: TerminalCharacter) {
+ self.viewport
+ .get_mut(self.cursor.y)
+ .unwrap()
+ .truncate(self.cursor.x);
+ if self.cursor.x < self.width - 1 {
+ let mut characters_to_append = vec![replace_with; self.width - self.cursor.x];
+ self.viewport
+ .get_mut(self.cursor.y)
+ .unwrap()
+ .append(&mut characters_to_append);
+ }
+ }
+ pub fn replace_characters_in_line_before_cursor(&mut self, replace_with: TerminalCharacter) {
+ let line_part = vec![replace_with; self.cursor.x];
+ let row = self.viewport.get_mut(self.cursor.y).unwrap();
+ row.replace_beginning_with(line_part);
+ }
+ pub fn clear_all_after_cursor(&mut self, replace_with: TerminalCharacter) {
+ let cursor_row = self.viewport.get_mut(self.cursor.y).unwrap();
+ cursor_row.truncate(self.cursor.x);
+ let mut replace_with_columns_in_cursor_row = vec![replace_with; self.width - self.cursor.x];
+ cursor_row.append(&mut replace_with_columns_in_cursor_row);
+
+ let replace_with_columns = vec![replace_with; self.width];
+ self.replace_characters_in_line_after_cursor(replace_with);
+ for row in self.viewport.iter_mut().skip(self.cursor.y + 1) {
+ row.replace_columns(replace_with_columns.clone());
+ }
+ }
+ pub fn clear_cursor_line(&mut self) {
+ self.viewport.get_mut(self.cursor.y).unwrap().truncate(0);
+ }
+ pub fn clear_all(&mut self, replace_with: TerminalCharacter) {
+ let replace_with_columns = vec![replace_with; self.width];
+ self.replace_characters_in_line_after_cursor(replace_with);
+ for row in self.viewport.iter_mut() {
+ row.replace_columns(replace_with_columns.clone());
+ }
+ }
+ fn pad_current_line_until(&mut self, position: usize) {
+ let current_row = self.viewport.get_mut(self.cursor.y).unwrap();
+ for _ in current_row.len()..position {
+ current_row.push(EMPTY_TERMINAL_CHARACTER);
+ }
+ }
+ fn pad_lines_until(&mut self, position: usize) {
+ for _ in self.viewport.len()..position {
+ self.viewport.push(Row::new().canonical());
+ }
+ }
+ pub fn move_cursor_to(&mut self, x: usize, y: usize) {
+ self.cursor.x = x;
+ self.cursor.y = y;
+ self.pad_lines_until(self.cursor.y + 1);
+ self.pad_current_line_until(self.cursor.x);
+ }
+ pub fn move_cursor_up(&mut self, count: usize) {
+ self.cursor.y = if self.cursor.y < count {
+ 0
+ } else {
+ self.cursor.y - count
+ };
+ }
+ pub fn move_cursor_up_with_scrolling(&mut self, count: usize) {
+ let (scroll_region_top, scroll_region_bottom) =
+ self.scroll_region.unwrap_or((0, self.height - 1));
+ for _ in 0..count {
+ let current_line_index = self.cursor.y;
+ if current_line_index == scroll_region_top {
+ // if we're at the top line, we create a new line and remove the last line that
+ // would otherwise overflow
+ self.viewport.remove(scroll_region_bottom);
+ self.viewport.insert(current_line_index, Row::new()); // TODO: .canonical() ?
+ } else if current_line_index > scroll_region_top
+ && current_line_index <= scroll_region_bottom
+ {
+ self.move_cursor_up(count);
+ }
+ }
+ }
+ pub fn move_cursor_down(&mut self, count: usize) {
+ let lines_to_add = if self.cursor.y + count > self.height - 1 {
+ (self.cursor.y + count) - (self.height - 1)
+ } else {
+ 0
+ };
+ self.cursor.y = if self.cursor.y + count > self.height - 1 {
+ self.height - 1
+ } else {
+ self.cursor.y + count
+ };
+ for _ in 0..lines_to_add {
+ self.add_canonical_line();
+ }
+ self.pad_lines_until(self.cursor.y);
+ }
+ pub fn move_cursor_back(&mut self, count: usize) {
+ if self.cursor.x < count {
+ self.cursor.x = 0;
+ } else {
+ self.cursor.x -= count;
+ }
+ }
+ pub fn hide_cursor(&mut self) {
+ self.cursor.is_hidden = true;
+ }
+ pub fn show_cursor(&mut self) {
+ self.cursor.is_hidden = false;
+ }
+ pub fn set_scroll_region(&mut self, top_line_index: usize, bottom_line_index: usize) {
+ self.scroll_region = Some((top_line_index, bottom_line_index));
+ }
+ pub fn clear_scroll_region(&mut self) {
+ self.scroll_region = None;
+ }
+ pub fn set_scroll_region_to_viewport_size(&mut self) {
+ self.scroll_region = Some((0, self.height - 1));
+ }
+ pub fn delete_lines_in_scroll_region(&mut self, count: usize) {
+ if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
+ let current_line_index = self.cursor.y;
+ if current_line_index >= scroll_region_top && current_line_index <= scroll_region_bottom
+ {
+ // when deleting lines inside the scroll region, we must make sure it stays the
+ // same size (and that other lines below it aren't shifted inside it)
+ // so we delete the current line(s) and add an empty line at the end of the scroll
+ // region
+ for _ in 0..count {
+ self.viewport.remove(current_line_index);
+ self.viewport
+ .insert(scroll_region_bottom, Row::new().canonical());
+ }
+ }
+ }
+ }
+ pub fn add_empty_lines_in_scroll_region(&mut self, count: usize) {
+ if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
+ let current_line_index = self.cursor.y;
+ if current_line_index >= scroll_region_top && current_line_index <= scroll_region_bottom
+ {
+ // when adding empty lines inside the scroll region, we must make sure it stays the
+ // same size and that lines don't "leak" outside of it
+ // so we add an empty line where the cursor currently is, and delete the last line
+ // of the scroll region
+ for _ in 0..count {
+ self.viewport.remove(scroll_region_bottom);
+ self.viewport
+ .insert(current_line_index, Row::new().canonical());
+ }
+ }
+ }
+ }
+ pub fn move_cursor_to_column(&mut self, column: usize) {
+ self.cursor.x = column;
+ self.pad_current_line_until(self.cursor.x);
+ }
+ pub fn move_cursor_to_line(&mut self, line: usize) {
+ self.cursor.y = line;
+ self.pad_lines_until(self.cursor.y + 1);
+ self.pad_current_line_until(self.cursor.x);
+ }
+ pub fn replace_with_empty_chars(&mut self, count: usize, empty_char_style: CharacterStyles) {
+ let mut empty_character = EMPTY_TERMINAL_CHARACTER;
+ empty_character.styles = empty_char_style;
+ let pad_until = std::cmp::min(self.width, self.cursor.x + count);
+ self.pad_current_line_until(pad_until);
+ let current_row = self.viewport.get_mut(self.cursor.y).unwrap();
+ for i in 0..count {
+ current_row.replace_character_at(empty_character, self.cursor.x + i);
+ }
+ }
+ pub fn erase_characters(&mut self, count: usize, empty_char_style: CharacterStyles) {
+ let mut empty_character = EMPTY_TERMINAL_CHARACTER;
+ empty_character.styles = empty_char_style;
+ let current_row = self.viewport.get_mut(self.cursor.y).unwrap();
+ for _ in 0..count {
+ current_row.delete_character(self.cursor.x);
+ }
+ let mut empty_space_to_append = vec![empty_character; count];
+ self.viewport
+ .get_mut(self.cursor.y)
+ .unwrap()
+ .append(&mut empty_space_to_append);
+ }
+}
+
+#[derive(Clone)]
+pub struct Row {
+ pub columns: Vec<TerminalCharacter>,
+ pub is_canonical: bool,
+}
+
+impl Debug for Row {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ for character in &self.columns {
+ write!(f, "{:?}", character)?;
+ }
+ Ok(())
+ }
+}
+
+impl Row {
+ pub fn new() -> Self {
+ Row {
+ columns: vec![],
+ is_canonical: false,
+ }
+ }
+ pub fn from_columns(columns: Vec<TerminalCharacter>) -> Self {
+ Row {
+ columns,
+ is_canonical: false,
+ }
+ }
+ pub fn from_rows(mut rows: Vec<Row>) -> Self {
+ if rows.is_empty() {
+ Row::new()
+ } else {
+ let mut first_row = rows.remove(0);
+ for row in rows.iter_mut() {
+ first_row.append(&mut row.columns);
+ }
+ first_row
+ }
+ }
+ pub fn with_character(mut self, terminal_character: TerminalCharacter) -> Self {
+ self.columns.push(terminal_character);
+ self
+ }
+ pub fn canonical(mut self) -> Self {
+ self.is_canonical = true;
+ self
+ }
+ pub fn add_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) {
+ match self.columns.len().cmp(&x) {
+ Ordering::Equal => self.columns.push(terminal_character),
+ Ordering::Less => {
+ self.columns.resize(x, EMPTY_TERMINAL_CHARACTER);
+ self.columns.push(terminal_character);
+ }
+ Ordering::Greater => {
+ // this is much more performant than remove/insert
+ self.columns.push(terminal_character);
+ self.columns.swap_remove(x);
+ }
+ }
+ }
+ pub fn replace_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) {
+ // this is much more performant than remove/insert
+ self.columns.push(terminal_character);
+ self.columns.swap_remove(x);
+ }
+ pub fn replace_columns(&mut self, columns: Vec<TerminalCharacter>) {
+ self.columns = columns;
+ }
+ pub fn push(&mut self, terminal_character: TerminalCharacter) {
+ self.columns.push(terminal_character);
+ }
+ pub fn truncate(&mut self, x: usize) {
+ self.columns.truncate(x);
+ }
+ pub fn append(&mut self, to_append: &mut Vec<TerminalCharacter>) {
+ self.columns.append(to_append);
+ }
+ pub fn replace_beginning_with(&mut self, mut line_part: Vec<TerminalCharacter>) {
+ drop(self.columns.drain(0..line_part.len()));
+ line_part.append(&mut self.columns);
+ self.columns = line_part;
+ }
+ pub fn len(&self) -> usize {
+ self.columns.len()
+ }
+ pub fn delete_character(&mut self, x: usize) {
+ self.columns.remove(x);
+ }
+ pub fn split_to_rows_of_length(&mut self, max_row_length: usize) -> Vec<Row> {
+ let mut parts: Vec<Row> = vec![];
+ let mut current_part: Vec<TerminalCharacter> = vec![];
+ for character in self.columns.drain(..) {
+ if current_part.len() == max_row_length {
+ parts.push(Row::from_columns(current_part));
+ current_part = vec![];
+ }
+ current_part.push(character);
+ }
+ if !current_part.is_empty() {
+ parts.push(Row::from_columns(current_part))
+ };
+ if !parts.is_empty() && self.is_canonical {
+ parts.get_mut(0).unwrap().is_canonical = true;
+ }
+ parts
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Cursor {
+ x: usize,
+ y: usize,
+ is_hidden: bool,
+}
+
+impl Cursor {
+ pub fn new(x: usize, y: usize) -> Self {
+ Cursor {
+ x,
+ y,
+ is_hidden: false,
+ }
+ }
+}
diff --git a/src/client/panes/mod.rs b/src/client/panes/mod.rs
new file mode 100644
index 00000000..4e7d6e80
--- /dev/null
+++ b/src/client/panes/mod.rs
@@ -0,0 +1,9 @@
+mod grid;
+mod plugin_pane;
+mod terminal_character;
+mod terminal_pane;
+
+pub use grid::*;
+pub use plugin_pane::*;
+pub use terminal_character::*;
+pub use terminal_pane::*;
diff --git a/src/client/panes/plugin_pane.rs b/src/client/panes/plugin_pane.rs
new file mode 100644
index 00000000..588cffd6
--- /dev/null
+++ b/src/client/panes/plugin_pane.rs
@@ -0,0 +1,191 @@
+#![allow(clippy::clippy::if_same_then_else)]
+
+use crate::{common::SenderWithContext, pty_bus::VteEvent, tab::Pane, wasm_vm::PluginInstruction};
+
+use std::{sync::mpsc::channel, unimplemented};
+
+use crate::panes::{PaneId, PositionAndSize};
+
+pub struct PluginPane {
+ pub pid: u32,
+ pub should_render: bool,
+ pub selectable: bool,
+ pub invisible_borders: bool,
+ pub position_and_size: PositionAndSize,
+ pub position_and_size_override: Option<PositionAndSize>,
+ pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ pub max_height: Option<usize>,
+}
+
+impl PluginPane {
+ pub fn new(
+ pid: u32,
+ position_and_size: PositionAndSize,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ ) -> Self {
+ Self {
+ pid,
+ should_render: true,
+ selectable: true,
+ invisible_borders: false,
+ position_and_size,
+ position_and_size_override: None,
+ send_plugin_instructions,
+ max_height: None,
+ }
+ }
+}
+
+impl Pane for PluginPane {
+ // FIXME: These position and size things should all be moved to default trait implementations,
+ // with something like a get_pos_and_sz() method underpinning all of them. Alternatively and
+ // preferably, just use an enum and not a trait object
+ fn x(&self) -> usize {
+ self.position_and_size_override
+ .unwrap_or(self.position_and_size)
+ .x
+ }
+ fn y(&self) -> usize {
+ self.position_and_size_override
+ .unwrap_or(self.position_and_size)
+ .y
+ }
+ fn rows(&self) -> usize {
+ self.position_and_size_override
+ .unwrap_or(self.position_and_size)
+ .rows
+ }
+ fn columns(&self) -> usize {
+ self.position_and_size_override
+ .unwrap_or(self.position_and_size)
+ .columns
+ }
+ fn reset_size_and_position_override(&mut self) {
+ self.position_and_size_override = None;
+ self.should_render = true;
+ }
+ fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
+ self.position_and_size = *position_and_size;
+ self.should_render = true;
+ }
+ // FIXME: This is obviously a bit outdated and needs the x and y moved into `size`
+ fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
+ let position_and_size_override = PositionAndSize {
+ x,
+ y,
+ rows: size.rows,
+ columns: size.columns,
+ };
+ self.position_and_size_override = Some(position_and_size_override);
+ self.should_render = true;
+ }
+ fn handle_event(&mut self, _event: VteEvent) {
+ unimplemented!()
+ }
+ fn cursor_coordinates(&self) -> Option<(usize, usize)> {
+ None
+ }
+ fn adjust_input_to_terminal(&self, _input_bytes: Vec<u8>) -> Vec<u8> {
+ unimplemented!() // FIXME: Shouldn't need this implmented?
+ }
+
+ fn position_and_size_override(&self) -> Option<PositionAndSize> {
+ self.position_and_size_override
+ }
+ fn should_render(&self) -> bool {
+ self.should_render
+ }
+ fn set_should_render(&mut self, should_render: bool) {
+ self.should_render = should_render;
+ }
+ fn selectable(&self) -> bool {
+ self.selectable
+ }
+ fn set_selectable(&mut self, selectable: bool) {
+ self.selectable = selectable;
+ }
+ fn set_invisible_borders(&mut self, invisible_borders: bool) {
+ self.invisible_borders = invisible_borders;
+ }
+ fn set_max_height(&mut self, max_height: usize) {
+ self.max_height = Some(max_height);
+ }
+ fn render(&mut self) -> Option<String> {
+ // if self.should_render {
+ if true {
+ // while checking should_render rather than rendering each pane every time
+ // is more performant, it causes some problems when the pane to the left should be
+ // rendered and has wide characters (eg. Chinese characters or emoji)
+ // as a (hopefully) temporary hack, we render all panes until we find a better solution
+ let (buf_tx, buf_rx) = channel();
+
+ self.send_plugin_instructions
+ .send(PluginInstruction::Draw(
+ buf_tx,
+ self.pid,
+ self.rows(),
+ self.columns(),
+ ))
+ .unwrap();
+
+ self.should_render = false;
+ Some(buf_rx.recv().unwrap())
+ } else {
+ None
+ }
+ }
+ fn pid(&self) -> PaneId {
+ PaneId::Plugin(self.pid)
+ }
+ fn reduce_height_down(&mut self, count: usize) {
+ self.position_and_size.y += count;
+ self.position_and_size.rows -= count;
+ self.should_render = true;
+ }
+ fn increase_height_down(&mut self, count: usize) {
+ self.position_and_size.rows += count;
+ self.should_render = true;
+ }
+ fn increase_height_up(&mut self, count: usize) {
+ self.position_and_size.y -= count;
+ self.position_and_size.rows += count;
+ self.should_render = true;
+ }
+ fn reduce_height_up(&mut self, count: usize) {
+ self.position_and_size.rows -= count;
+ self.should_render = true;
+ }
+ fn reduce_width_right(&mut self, count: usize) {
+ self.position_and_size.x += count;
+ self.position_and_size.columns -= count;
+ self.should_render = true;
+ }
+ fn reduce_width_left(&mut self, count: usize) {
+ self.position_and_size.columns -= count;
+ self.should_render = true;
+ }
+ fn increase_width_left(&mut self, count: usize) {
+ self.position_and_size.x -= count;
+ self.position_and_size.columns += count;
+ self.should_render = true;
+ }
+ fn increase_width_right(&mut self, count: usize) {
+ self.position_and_size.columns += count;
+ self.should_render = true;
+ }
+ fn scroll_up(&mut self, _count: usize) {
+ unimplemented!()
+ }
+ fn scroll_down(&mut self, _count: usize) {
+ unimplemented!()
+ }
+ fn clear_scroll(&mut self) {
+ unimplemented!()
+ }
+ fn max_height(&self) -> Option<usize> {
+ self.max_height
+ }
+ fn invisible_borders(&self) -> bool {
+ self.invisible_borders
+ }
+}
diff --git a/src/client/panes/terminal_character.rs b/src/client/panes/terminal_character.rs
new file mode 100644
index 00000000..80c3162b
--- /dev/null
+++ b/src/client/panes/terminal_character.rs
@@ -0,0 +1,572 @@
+use crate::utils::logging::debug_log_to_file;
+use ::std::fmt::{self, Debug, Display, Formatter};
+
+pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
+ character: ' ',
+ styles: CharacterStyles {
+ foreground: Some(AnsiCode::Reset),
+ background: Some(AnsiCode::Reset),
+ strike: Some(AnsiCode::Reset),
+ hidden: Some(AnsiCode::Reset),
+ reverse: Some(AnsiCode::Reset),
+ slow_blink: Some(AnsiCode::Reset),
+ fast_blink: Some(AnsiCode::Reset),
+ underline: Some(AnsiCode::Reset),
+ bold: Some(AnsiCode::Reset),
+ dim: Some(AnsiCode::Reset),
+ italic: Some(AnsiCode::Reset),
+ },
+};
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum AnsiCode {
+ On,
+ Reset,
+ NamedColor(NamedColor),
+ RGBCode((u8, u8, u8)),
+ ColorIndex(u8),
+}
+
+#[derive(Clone, Copy, Eq, PartialEq, Debug)]
+pub enum NamedColor {
+ Black,
+ Red,
+ Green,
+ Yellow,
+ Blue,
+ Magenta,
+ Cyan,
+ White,
+}
+
+impl NamedColor {
+ fn to_foreground_ansi_code(&self) -> String {
+ match self {
+ NamedColor::Black => format!("{}", 30),
+ NamedColor::Red => format!("{}", 31),
+ NamedColor::Green => format!("{}", 32),
+ NamedColor::Yellow => format!("{}", 33),
+ NamedColor::Blue => format!("{}", 34),
+ NamedColor::Magenta => format!("{}", 35),
+ NamedColor::Cyan => format!("{}", 36),
+ NamedColor::White => format!("{}", 37),
+ }
+ }
+ fn to_background_ansi_code(&self) -> String {
+ match self {
+ NamedColor::Black => format!("{}", 40),
+ NamedColor::Red => format!("{}", 41),
+ NamedColor::Green => format!("{}", 42),
+ NamedColor::Yellow => format!("{}", 43),
+ NamedColor::Blue => format!("{}", 44),
+ NamedColor::Magenta => format!("{}", 45),
+ NamedColor::Cyan => format!("{}", 46),
+ NamedColor::White => format!("{}", 47),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct CharacterStyles {
+ pub foreground: Option<AnsiCode>,
+ pub background: Option<AnsiCode>,
+ pub strike: Option<AnsiCode>,
+ pub hidden: Option<AnsiCode>,
+ pub reverse: Option<AnsiCode>,
+ pub slow_blink: Option<AnsiCode>,
+ pub fast_blink: Option<AnsiCode>,
+ pub underline: Option<AnsiCode>,
+ pub bold: Option<AnsiCode>,
+ pub dim: Option<AnsiCode>,
+ pub italic: Option<AnsiCode>,
+}
+
+impl CharacterStyles {
+ pub fn new() -> Self {
+ CharacterStyles {
+ foreground: None,
+ background: None,
+ strike: None,
+ hidden: None,
+ reverse: None,
+ slow_blink: None,
+ fast_blink: None,
+ underline: None,
+ bold: None,
+ dim: None,
+ italic: None,
+ }
+ }
+ pub fn foreground(mut self, foreground_code: Option<AnsiCode>) -> Self {
+ self.foreground = foreground_code;
+ self
+ }
+ pub fn background(mut self, background_code: Option<AnsiCode>) -> Self {
+ self.background = background_code;
+ self
+ }
+ pub fn bold(mut self, bold_code: Option<AnsiCode>) -> Self {
+ self.bold = bold_code;
+ self
+ }
+ pub fn dim(mut self, dim_code: Option<AnsiCode>) -> Self {
+ self.dim = dim_code;
+ self
+ }
+ pub fn italic(mut self, italic_code: Option<AnsiCode>) -> Self {
+ self.italic = italic_code;
+ self
+ }
+ pub fn underline(mut self, underline_code: Option<AnsiCode>) -> Self {
+ self.underline = underline_code;
+ self
+ }
+ pub fn blink_slow(mut self, slow_blink_code: Option<AnsiCode>) -> Self {
+ self.slow_blink = slow_blink_code;
+ self
+ }
+ pub fn blink_fast(mut self, fast_blink_code: Option<AnsiCode>) -> Self {
+ self.fast_blink = fast_blink_code;
+ self
+ }
+ pub fn reverse(mut self, reverse_code: Option<AnsiCode>) -> Self {
+ self.reverse = reverse_code;
+ self
+ }
+ pub fn hidden(mut self, hidden_code: Option<AnsiCode>) -> Self {
+ self.hidden = hidden_code;
+ self
+ }
+ pub fn strike(mut self, strike_code: Option<AnsiCode>) -> Self {
+ self.strike = strike_code;
+ self
+ }
+ pub fn clear(&mut self) {
+ self.foreground = None;
+ self.background = None;
+ self.strike = None;
+ self.hidden = None;
+ self.reverse = None;
+ self.slow_blink = None;
+ self.fast_blink = None;
+ self.underline = None;
+ self.bold = None;
+ self.dim = None;
+ self.italic = None;
+ }
+ pub fn update_and_return_diff(
+ &mut self,
+ new_styles: &CharacterStyles,
+ ) -> Option<CharacterStyles> {
+ let mut diff: Option<CharacterStyles> = None;
+
+ if new_styles.foreground == Some(AnsiCode::Reset)
+ && new_styles.background == Some(AnsiCode::Reset)
+ && new_styles.strike == Some(AnsiCode::Reset)
+ && new_styles.hidden == Some(AnsiCode::Reset)
+ && new_styles.reverse == Some(AnsiCode::Reset)
+ && new_styles.fast_blink == Some(AnsiCode::Reset)
+ && new_styles.slow_blink == Some(AnsiCode::Reset)
+ && new_styles.underline == Some(AnsiCode::Reset)
+ && new_styles.bold == Some(AnsiCode::Reset)
+ && new_styles.dim == Some(AnsiCode::Reset)
+ && new_styles.italic == Some(AnsiCode::Reset)
+ {
+ self.foreground = Some(AnsiCode::Reset);
+ self.background = Some(AnsiCode::Reset);
+ self.strike = Some(AnsiCode::Reset);
+ self.hidden = Some(AnsiCode::Reset);
+ self.reverse = Some(AnsiCode::Reset);
+ self.fast_blink = Some(AnsiCode::Reset);
+ self.slow_blink = Some(AnsiCode::Reset);
+ self.underline = Some(AnsiCode::Reset);
+ self.bold = Some(AnsiCode::Reset);
+ self.dim = Some(AnsiCode::Reset);
+ self.italic = Some(AnsiCode::Reset);
+ return Some(*new_styles);
+ };
+
+ if self.foreground != new_styles.foreground {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.foreground(new_styles.foreground));
+ self.foreground = new_styles.foreground;
+ } else {
+ diff = Some(CharacterStyles::new().foreground(new_styles.foreground));
+ self.foreground = new_styles.foreground;
+ }
+ }
+ if self.background != new_styles.background {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.background(new_styles.background));
+ self.background = new_styles.background;
+ } else {
+ diff = Some(CharacterStyles::new().background(new_styles.background));
+ self.background = new_styles.background;
+ }
+ }
+ if self.strike != new_styles.strike {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.strike(new_styles.strike));
+ self.strike = new_styles.strike;
+ } else {
+ diff = Some(CharacterStyles::new().strike(new_styles.strike));
+ self.strike = new_styles.strike;
+ }
+ }
+ if self.hidden != new_styles.hidden {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.hidden(new_styles.hidden));
+ self.hidden = new_styles.hidden;
+ } else {
+ diff = Some(CharacterStyles::new().hidden(new_styles.hidden));
+ self.hidden = new_styles.hidden;
+ }
+ }
+ if self.reverse != new_styles.reverse {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.reverse(new_styles.reverse));
+ self.reverse = new_styles.reverse;
+ } else {
+ diff = Some(CharacterStyles::new().reverse(new_styles.reverse));
+ self.reverse = new_styles.reverse;
+ }
+ }
+ if self.slow_blink != new_styles.slow_blink {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.blink_slow(new_styles.slow_blink));
+ self.slow_blink = new_styles.slow_blink;
+ } else {
+ diff = Some(CharacterStyles::new().blink_slow(new_styles.slow_blink));
+ self.slow_blink = new_styles.slow_blink;
+ }
+ }
+ if self.fast_blink != new_styles.fast_blink {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.blink_fast(new_styles.fast_blink));
+ self.fast_blink = new_styles.fast_blink;
+ } else {
+ diff = Some(CharacterStyles::new().blink_fast(new_styles.fast_blink));
+ self.fast_blink = new_styles.fast_blink;
+ }
+ }
+ if self.underline != new_styles.underline {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.underline(new_styles.underline));
+ self.underline = new_styles.underline;
+ } else {
+ diff = Some(CharacterStyles::new().underline(new_styles.underline));
+ self.underline = new_styles.underline;
+ }
+ }
+ if self.bold != new_styles.bold {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.bold(new_styles.bold));
+ self.bold = new_styles.bold;
+ } else {
+ diff = Some(CharacterStyles::new().bold(new_styles.bold));
+ self.bold = new_styles.bold;
+ }
+ }
+ if self.dim != new_styles.dim {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.dim(new_styles.dim));
+ self.dim = new_styles.dim;
+ } else {
+ diff = Some(CharacterStyles::new().dim(new_styles.dim));
+ self.dim = new_styles.dim;
+ }
+ }
+ if self.italic != new_styles.italic {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.italic(new_styles.italic));
+ self.italic = new_styles.italic;
+ } else {
+ diff = Some(CharacterStyles::new().italic(new_styles.italic));
+ self.italic = new_styles.italic;
+ }
+ }
+ diff
+ }
+ pub fn reset_all(&mut self) {
+ self.foreground = Some(AnsiCode::Reset);
+ self.background = Some(AnsiCode::Reset);
+ self.bold = Some(AnsiCode::Reset);
+ self.dim = Some(AnsiCode::Reset);
+ self.italic = Some(AnsiCode::Reset);
+ self.underline = Some(AnsiCode::Reset);
+ self.slow_blink = Some(AnsiCode::Reset);
+ self.fast_blink = Some(AnsiCode::Reset);
+ self.reverse = Some(AnsiCode::Reset);
+ self.hidden = Some(AnsiCode::Reset);
+ self.strike = Some(AnsiCode::Reset);
+ }
+ pub fn add_style_from_ansi_params(&mut self, ansi_params: &[i64]) {
+ let mut params_used = 1; // if there's a parameter, it is always used
+ match ansi_params {
+ [] | [0, ..] => self.reset_all(),
+ [1, ..] => *self = self.bold(Some(AnsiCode::On)),
+ [2, ..] => *self = self.dim(Some(AnsiCode::On)),
+ [3, ..] => *self = self.italic(Some(AnsiCode::On)),
+ [4, ..] => *self = self.underline(Some(AnsiCode::On)),
+ [5, ..] => *self = self.blink_slow(Some(AnsiCode::On)),
+ [6, ..] => *self = self.blink_fast(Some(AnsiCode::On)),
+ [7, ..] => *self = self.reverse(Some(AnsiCode::On)),
+ [8, ..] => *self = self.hidden(Some(AnsiCode::On)),
+ [9, ..] => *self = self.strike(Some(AnsiCode::On)),
+ [21, ..] => *self = self.bold(Some(AnsiCode::Reset)),
+ [22, ..] => {
+ *self = self.bold(Some(AnsiCode::Reset));
+ *self = self.dim(Some(AnsiCode::Reset));
+ }
+ [23, ..] => *self = self.italic(Some(AnsiCode::Reset)),
+ [24, ..] => *self = self.underline(Some(AnsiCode::Reset)),
+ [25, ..] => {
+ *self = self.blink_slow(Some(AnsiCode::Reset));
+ *self = self.blink_fast(Some(AnsiCode::Reset));
+ }
+ [27, ..] => *self = self.reverse(Some(AnsiCode::Reset)),
+ [28, ..] => *self = self.hidden(Some(AnsiCode::Reset)),
+ [29, ..] => *self = self.strike(Some(AnsiCode::Reset)),
+ [30, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Black))),
+ [31, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Red))),
+ [32, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Green))),
+ [33, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Yellow))),
+ [34, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Blue))),
+ [35, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Magenta))),
+ [36, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Cyan))),
+ [37, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::White))),
+ [38, 2, ..] => {
+ let ansi_code = AnsiCode::RGBCode((
+ *ansi_params.get(2).unwrap() as u8,
+ *ansi_params.get(3).unwrap() as u8,
+ *ansi_params.get(4).unwrap() as u8,
+ ));
+ *self = self.foreground(Some(ansi_code));
+ params_used += 4 // one for the indicator (2 in this case) and three for the rgb code
+ }
+ [38, 5, ..] => {
+ let ansi_code = AnsiCode::ColorIndex(*ansi_params.get(2).unwrap() as u8);
+ *self = self.foreground(Some(ansi_code));
+ params_used += 2 // one for the indicator (5 in this case) and one for the color index
+ }
+ [38, ..] => {
+ // this is a bug
+ // it means we got a color encoding we don't know how to handle (or is invalid)
+ params_used += 1; // even if it's a bug, let's not create an endless loop, eh?
+ }
+ [39, ..] => *self = self.foreground(Some(AnsiCode::Reset)),
+ [40, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Black))),
+ [41, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Red))),
+ [42, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Green))),
+ [43, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Yellow))),
+ [44, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Blue))),
+ [45, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Magenta))),
+ [46, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Cyan))),
+ [47, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::White))),
+ [48, 2, ..] => {
+ let ansi_code = AnsiCode::RGBCode((
+ *ansi_params.get(2).unwrap() as u8,
+ *ansi_params.get(3).unwrap() as u8,
+ *ansi_params.get(4).unwrap() as u8,
+ ));
+ *self = self.background(Some(ansi_code));
+ params_used += 4 // one for the indicator (2 in this case) and three for the rgb code
+ }
+ [48, 5, ..] => {
+ let ansi_code = AnsiCode::ColorIndex(*ansi_params.get(2).unwrap() as u8);
+ *self = self.background(Some(ansi_code));
+ params_used += 2 // one for the indicator (5 in this case) and one for the color index
+ }
+ [48, ..] => {
+ // this is a bug
+ // it means we got a color encoding we don't know how to handle (or is invalid)
+ params_used += 1; // even if it's a bug, let's not create an endless loop, eh?
+ }
+ [49, ..] => *self = self.background(Some(AnsiCode::Reset)),
+ _ => {
+ // if this happens, it's a bug
+ let _ = debug_log_to_file(format!("unhandled csi m code {:?}", ansi_params));
+ return;
+ }
+ }
+ if let Some(next_params) = ansi_params.get(params_used..) {
+ if !next_params.is_empty() {
+ self.add_style_from_ansi_params(next_params);
+ }
+ }
+ }
+}
+
+impl Display for CharacterStyles {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ if self.foreground == Some(AnsiCode::Reset)
+ && self.background == Some(AnsiCode::Reset)
+ && self.strike == Some(AnsiCode::Reset)
+ && self.hidden == Some(AnsiCode::Reset)
+ && self.reverse == Some(AnsiCode::Reset)
+ && self.fast_blink == Some(AnsiCode::Reset)
+ && self.slow_blink == Some(AnsiCode::Reset)
+ && self.underline == Some(AnsiCode::Reset)
+ && self.bold == Some(AnsiCode::Reset)
+ && self.dim == Some(AnsiCode::Reset)
+ && self.italic == Some(AnsiCode::Reset)
+ {
+ write!(f, "\u{1b}[m")?; // reset all
+ return Ok(());
+ }
+ if let Some(ansi_code) = self.foreground {
+ match ansi_code {
+ AnsiCode::RGBCode((r, g, b)) => {
+ write!(f, "\u{1b}[38;2;{};{};{}m", r, g, b)?;
+ }
+ AnsiCode::ColorIndex(color_index) => {
+ write!(f, "\u{1b}[38;5;{}m", color_index)?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[39m")?;
+ }
+ AnsiCode::NamedColor(named_color) => {
+ write!(f, "\u{1b}[{}m", named_color.to_foreground_ansi_code())?;
+ }
+ _ => {}
+ }
+ };
+ if let Some(ansi_code) = self.background {
+ match ansi_code {
+ AnsiCode::RGBCode((r, g, b)) => {
+ write!(f, "\u{1b}[48;2;{};{};{}m", r, g, b)?;
+ }
+ AnsiCode::ColorIndex(color_index) => {
+ write!(f, "\u{1b}[48;5;{}m", color_index)?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[49m")?;
+ }
+ AnsiCode::NamedColor(named_color) => {
+ write!(f, "\u{1b}[{}m", named_color.to_background_ansi_code())?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.strike {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[9m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[29m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.hidden {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[8m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[28m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.reverse {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[7m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[27m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.fast_blink {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[6m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[25m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.slow_blink {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[5m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[25m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.bold {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[1m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[22m\u{1b}[24m")?;
+ // TODO: this cancels bold + underline, if this behaviour is indeed correct, we
+ // need to properly handle it in the struct methods etc like dim
+ }
+ _ => {}
+ }
+ }
+ // notice the order is important here, bold must be before underline
+ // because the bold reset also resets underline, and would override it
+ // otherwise
+ if let Some(ansi_code) = self.underline {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[4m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[24m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.dim {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[2m")?;
+ }
+ AnsiCode::Reset => {
+ if let Some(AnsiCode::Reset) = self.bold {
+ // we only reset dim if both dim and bold should be reset
+ write!(f, "\u{1b}[22m")?;
+ }
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.italic {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[3m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[23m")?;
+ }
+ _ => {}
+ }
+ };
+ Ok(())
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct TerminalCharacter {
+ pub character: char,
+ pub styles: CharacterStyles,
+}
+
+impl ::std::fmt::Debug for TerminalCharacter {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.character)
+ }
+}
diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs
new file mode 100644
index 00000000..21244fab
--- /dev/null
+++ b/src/client/panes/terminal_pane.rs
@@ -0,0 +1,675 @@
+#![allow(clippy::clippy::if_same_then_else)]
+
+use crate::tab::Pane;
+use ::nix::pty::Winsize;
+use ::std::os::unix::io::RawFd;
+use ::vte::Perform;
+use std::fmt::Debug;
+
+use crate::panes::grid::Grid;
+use crate::panes::terminal_character::{
+ CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
+};
+use crate::utils::logging::debug_log_to_file;
+use crate::VteEvent;
+
+#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
+pub enum PaneId {
+ Terminal(RawFd),
+ Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
+}
+#[derive(Clone, Copy, Debug, Default)]
+pub struct PositionAndSize {
+ pub x: usize,
+ pub y: usize,
+ pub rows: usize,
+ pub columns: usize,
+}
+
+impl From<Winsize> for PositionAndSize {
+ fn from(winsize: Winsize) -> PositionAndSize {
+ PositionAndSize {
+ columns: winsize.ws_col as usize,
+ rows: winsize.ws_row as usize,
+ ..Default::default()
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct TerminalPane {
+ pub grid: Grid,
+ pub alternative_grid: Option<Grid>, // for 1049h/l instructions which tell us to switch between these two
+ pub pid: RawFd,
+ pub should_render: bool,
+ pub selectable: bool,
+ pub position_and_size: PositionAndSize,
+ pub position_and_size_override: Option<PositionAndSize>,
+ pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "")
+ pub max_height: Option<usize>,
+ pending_styles: CharacterStyles,
+ clear_viewport_before_rendering: bool,
+}
+
+impl Pane for TerminalPane {
+ fn x(&self) -> usize {
+ self.get_x()
+ }
+ fn y(&self) -> usize {
+ self.get_y()
+ }
+ fn rows(&self) -> usize {
+ self.get_rows()
+ }
+ fn columns(&self) -> usize {
+ self.get_columns()
+ }
+ fn reset_size_and_position_override(&mut self) {
+ self.position_and_size_override = None;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
+ self.position_and_size.columns = position_and_size.columns;
+ self.position_and_size.rows = position_and_size.rows;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
+ let position_and_size_override = PositionAndSize {
+ x,
+ y,
+ rows: size.rows,
+ columns: size.columns,
+ };
+ self.position_and_size_override = Some(position_and_size_override);
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn handle_event(&mut self, event: VteEvent) {
+ match event {
+ VteEvent::Print(c) => {
+ self.print(c);
+ self.mark_for_rerender();
+ }
+ VteEvent::Execute(byte) => {
+ self.execute(byte);
+ }
+ VteEvent::Hook(params, intermediates, ignore, c) => {
+ self.hook(&params, &intermediates, ignore, c);
+ }
+ VteEvent::Put(byte) => {
+ self.put(byte);
+ }
+ VteEvent::Unhook => {
+ self.unhook();
+ }
+ VteEvent::OscDispatch(params, bell_terminated) => {
+ let params: Vec<&[u8]> = params.iter().map(|p| &p[..]).collect();
+ self.osc_dispatch(&params[..], bell_terminated);
+ }
+ VteEvent::CsiDispatch(params, intermediates, ignore, c) => {
+ self.csi_dispatch(&params, &intermediates, ignore, c);
+ }
+ VteEvent::EscDispatch(intermediates, ignore, byte) => {
+ self.esc_dispatch(&intermediates, ignore, byte);
+ }
+ }
+ }
+ fn cursor_coordinates(&self) -> Option<(usize, usize)> {
+ // (x, y)
+ self.grid.cursor_coordinates()
+ }
+ fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8> {
+ // there are some cases in which the terminal state means that input sent to it
+ // needs to be adjusted.
+ // here we match against those cases - if need be, we adjust the input and if not
+ // we send back the original input
+ match input_bytes.as_slice() {
+ [27, 91, 68] => {
+ // left arrow
+ if self.cursor_key_mode {
+ // please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
+ // some editors will not show this
+ return "OD".as_bytes().to_vec();
+ }
+ }
+ [27, 91, 67] => {
+ // right arrow
+ if self.cursor_key_mode {
+ // please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
+ // some editors will not show this
+ return "OC".as_bytes().to_vec();
+ }
+ }
+ [27, 91, 65] => {
+ // up arrow
+ if self.cursor_key_mode {
+ // please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
+ // some editors will not show this
+ return "OA".as_bytes().to_vec();
+ }
+ }
+ [27, 91, 66] => {
+ // down arrow
+ if self.cursor_key_mode {
+ // please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
+ // some editors will not show this
+ return "OB".as_bytes().to_vec();
+ }
+ }
+ _ => {}
+ };
+ input_bytes
+ }
+
+ fn position_and_size_override(&self) -> Option<PositionAndSize> {
+ self.position_and_size_override
+ }
+ fn should_render(&self) -> bool {
+ self.should_render
+ }
+ fn set_should_render(&mut self, should_render: bool) {
+ self.should_render = should_render;
+ }
+ fn selectable(&self) -> bool {
+ self.selectable
+ }
+ fn set_selectable(&mut self, selectable: bool) {
+ self.selectable = selectable;
+ }
+ fn set_max_height(&mut self, max_height: usize) {
+ self.max_height = Some(max_height);
+ }
+ fn set_invisible_borders(&mut self, _invisible_borders: bool) {
+ unimplemented!();
+ }
+ fn max_height(&self) -> Option<usize> {
+ self.max_height
+ }
+ fn render(&mut self) -> Option<String> {
+ // if self.should_render {
+ if true {
+ // while checking should_render rather than rendering each pane every time
+ // is more performant, it causes some problems when the pane to the left should be
+ // rendered and has wide characters (eg. Chinese characters or emoji)
+ // as a (hopefully) temporary hack, we render all panes until we find a better solution
+ let mut vte_output = String::new();
+ let buffer_lines = &self.read_buffer_as_lines();
+ let display_cols = self.get_columns();
+ let mut character_styles = CharacterStyles::new();
+ if self.clear_viewport_before_rendering {
+ for line_index in 0..self.grid.height {
+ let x = self.get_x();
+ let y = self.get_y();
+ vte_output = format!(
+ "{}\u{1b}[{};{}H\u{1b}[m",
+ vte_output,
+ y + line_index + 1,
+ x + 1
+ ); // goto row/col and reset styles
+ for _col_index in 0..self.grid.width {
+ vte_output.push(EMPTY_TERMINAL_CHARACTER.character);
+ }
+ }
+ self.clear_viewport_before_rendering = false;
+ }
+ for (row, line) in buffer_lines.iter().enumerate() {
+ let x = self.get_x();
+ let y = self.get_y();
+ vte_output = format!("{}\u{1b}[{};{}H\u{1b}[m", vte_output, y + row + 1, x + 1); // goto row/col and reset styles
+ for (col, t_character) in line.iter().enumerate() {
+ if col < display_cols {
+ // in some cases (eg. while resizing) some characters will spill over
+ // before they are corrected by the shell (for the prompt) or by reflowing
+ // lines
+ if let Some(new_styles) =
+ character_styles.update_and_return_diff(&t_character.styles)
+ {
+ // the terminal keeps the previous styles as long as we're in the same
+ // line, so we only want to update the new styles here (this also
+ // includes resetting previous styles as needed)
+ vte_output = format!("{}{}", vte_output, new_styles);
+ }
+ vte_output.push(t_character.character);
+ }
+ }
+ character_styles.clear();
+ }
+ self.mark_for_rerender();
+ Some(vte_output)
+ } else {
+ None
+ }
+ }
+ fn pid(&self) -> PaneId {
+ PaneId::Terminal(self.pid)
+ }
+ fn reduce_height_down(&mut self, count: usize) {
+ self.position_and_size.y += count;
+ self.position_and_size.rows -= count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn increase_height_down(&mut self, count: usize) {
+ self.position_and_size.rows += count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn increase_height_up(&mut self, count: usize) {
+ self.position_and_size.y -= count;
+ self.position_and_size.rows += count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn reduce_height_up(&mut self, count: usize) {
+ self.position_and_size.rows -= count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn reduce_width_right(&mut self, count: usize) {
+ self.position_and_size.x += count;
+ self.position_and_size.columns -= count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn reduce_width_left(&mut self, count: usize) {
+ self.position_and_size.columns -= count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn increase_width_left(&mut self, count: usize) {
+ self.position_and_size.x -= count;
+ self.position_and_size.columns += count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn increase_width_right(&mut self, count: usize) {
+ self.position_and_size.columns += count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn scroll_up(&mut self, count: usize) {
+ self.grid.move_viewport_up(count);
+ self.mark_for_rerender();
+ }
+ fn scroll_down(&mut self, count: usize) {
+ self.grid.move_viewport_down(count);
+ self.mark_for_rerender();
+ }
+ fn clear_scroll(&mut self) {
+ self.grid.reset_viewport();
+ self.mark_for_rerender();
+ }
+}
+
+impl TerminalPane {
+ pub fn new(pid: RawFd, position_and_size: PositionAndSize) -> TerminalPane {
+ let grid = Grid::new(position_and_size.rows, position_and_size.columns);
+ let pending_styles = CharacterStyles::new();
+
+ TerminalPane {
+ pid,
+ grid,
+ alternative_grid: None,
+ should_render: true,
+ selectable: true,
+ pending_styles,
+ position_and_size,
+ position_and_size_override: None,
+ cursor_key_mode: false,
+ clear_viewport_before_rendering: false,
+ max_height: None,
+ }
+ }
+ pub fn mark_for_rerender(&mut self) {
+ self.should_render = true;
+ }
+ pub fn get_x(&self) -> usize {
+ match self.position_and_size_override {
+ Some(position_and_size_override) => position_and_size_override.x,
+ None => self.position_and_size.x as usize,
+ }
+ }
+ pub fn get_y(&self) -> usize {
+ match self.position_and_size_override {
+ Some(position_and_size_override) => position_and_size_override.y,
+ None => self.position_and_size.y as usize,
+ }
+ }
+ pub fn get_columns(&self) -> usize {
+ match &self.position_and_size_override.as_ref() {
+ Some(position_and_size_override) => position_and_size_override.columns,
+ None => self.position_and_size.columns as usize,
+ }
+ }
+ pub fn get_rows(&self) -> usize {
+ match &self.position_and_size_override.as_ref() {
+ Some(position_and_size_override) => position_and_size_override.rows,
+ None => self.position_and_size.rows as usize,
+ }
+ }
+ fn reflow_lines(&mut self) {
+ let rows = self.get_rows();
+ let columns = self.get_columns();
+ self.grid.change_size(rows, columns);
+ if let Some(alternative_grid) = self.alternative_grid.as_mut() {
+ alternative_grid.change_size(rows, columns);
+ }
+ }
+
+ pub fn read_buffer_as_lines(&self) -> Vec<Vec<TerminalCharacter>> {
+ self.grid.as_character_lines()
+ }
+ #[cfg(test)]
+ pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
+ // (x, y)
+ self.grid.cursor_coordinates()
+ }
+ pub fn rotate_scroll_region_up(&mut self, count: usize) {
+ self.grid.rotate_scroll_region_up(count);
+ self.mark_for_rerender();
+ }
+ pub fn rotate_scroll_region_down(&mut self, count: usize) {
+ self.grid.rotate_scroll_region_down(count);
+ self.mark_for_rerender();
+ }
+ fn add_newline(&mut self) {
+ self.grid.add_canonical_line();
+ // self.reset_all_ansi_codes(); // TODO: find out if we should be resetting here or not
+ self.mark_for_rerender();
+ }
+ fn move_to_beginning_of_line(&mut self) {
+ self.grid.move_cursor_to_beginning_of_line();
+ }
+ fn move_cursor_backwards(&mut self, count: usize) {
+ self.grid.move_cursor_backwards(count);
+ }
+ fn _reset_all_ansi_codes(&mut self) {
+ self.pending_styles.clear();
+ }
+}
+
+impl vte::Perform for TerminalPane {
+ fn print(&mut self, c: char) {
+ // apparently, building TerminalCharacter like this without a "new" method
+ // is a little faster
+ let terminal_character = TerminalCharacter {
+ character: c,
+ styles: self.pending_styles,
+ };
+ self.grid.add_character(terminal_character);
+ }
+
+ fn execute(&mut self, byte: u8) {
+ match byte {
+ 8 => {
+ // backspace
+ self.move_cursor_backwards(1);
+ }
+ 9 => {
+ // tab
+ let terminal_tab_character = TerminalCharacter {
+ character: '\t',
+ styles: self.pending_styles,
+ };
+ // TODO: handle better with line wrapping
+ self.grid.add_character(terminal_tab_character);
+ }
+ 10 => {
+ // 0a, newline
+ self.add_newline();
+ }
+ 13 => {
+ // 0d, carriage return
+ self.move_to_beginning_of_line();
+ }
+ _ => {}
+ }
+ }
+
+ fn hook(&mut self, _params: &[i64], _intermediates: &[u8], _ignore: bool, _c: char) {
+ // TBD
+ }
+
+ fn put(&mut self, _byte: u8) {
+ // TBD
+ }
+
+ fn unhook(&mut self) {
+ // TBD
+ }
+
+ fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {
+ // TBD
+ }
+
+ fn csi_dispatch(&mut self, params: &[i64], _intermediates: &[u8], _ignore: bool, c: char) {
+ if c == 'm' {
+ self.pending_styles.add_style_from_ansi_params(params);
+ } else if c == 'C' {
+ // move cursor forward
+ let move_by = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.grid.move_cursor_forward_until_edge(move_by);
+ } else if c == 'K' {
+ // clear line (0 => right, 1 => left, 2 => all)
+ if params[0] == 0 {
+ let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
+ char_to_replace.styles = self.pending_styles;
+ self.grid
+ .replace_characters_in_line_after_cursor(char_to_replace);
+ } else if params[0] == 1 {
+ let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
+ char_to_replace.styles = self.pending_styles;
+ self.grid
+ .replace_characters_in_line_before_cursor(char_to_replace);
+ } else if params[0] == 2 {
+ self.grid.clear_cursor_line();
+ }
+ } else if c == 'J' {
+ // clear all (0 => below, 1 => above, 2 => all, 3 => saved)
+ let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
+ char_to_replace.styles = self.pending_styles;
+ if params[0] == 0 {
+ self.grid.clear_all_after_cursor(char_to_replace);
+ } else if params[0] == 2 {
+ self.grid.clear_all(char_to_replace);
+ }
+ // TODO: implement 1
+ } else if c == 'H' {
+ // goto row/col
+ // we subtract 1 from the row/column because these are 1 indexed
+ // (except when they are 0, in which case they should be 1
+ // don't look at me, I don't make the rules)
+ let (row, col) = if params.len() == 1 {
+ if params[0] == 0 {
+ (0, params[0] as usize)
+ } else {
+ (params[0] as usize - 1, params[0] as usize)
+ }
+ } else if params[0] == 0 {
+ (0, params[1] as usize - 1)
+ } else {
+ (params[0] as usize - 1, params[1] as usize - 1)
+ };
+ self.grid.move_cursor_to(col, row);
+ } else if c == 'A' {
+ // move cursor up until edge of screen
+ let move_up_count = if params[0] == 0 { 1 } else { params[0] };
+ self.grid.move_cursor_up(move_up_count as usize);
+ } else if c == 'B' {
+ // move cursor down until edge of screen
+ let move_down_count = if params[0] == 0 { 1 } else { params[0] };
+ self.grid.move_cursor_down(move_down_count as usize);
+ } else if c == 'D' {
+ let move_back_count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.grid.move_cursor_back(move_back_count);
+ } else if c == 'l' {
+ let first_intermediate_is_questionmark = match _intermediates.get(0) {
+ Some(b'?') => true,
+ None => false,
+ _ => false,
+ };
+ if first_intermediate_is_questionmark {
+ match params.get(0) {
+ Some(&1049) => {
+ if let Some(alternative_grid) = self.alternative_grid.as_mut() {
+ std::mem::swap(&mut self.grid, alternative_grid);
+ }
+ self.alternative_grid = None;
+ self.clear_viewport_before_rendering = true;
+ self.mark_for_rerender();
+ }
+ Some(&25) => {
+ self.grid.hide_cursor();
+ self.mark_for_rerender();
+ }
+ Some(&1) => {
+ self.cursor_key_mode = false;
+ }
+ _ => {}
+ };
+ }
+ } else if c == 'h' {
+ let first_intermediate_is_questionmark = match _intermediates.get(0) {
+ Some(b'?') => true,
+ None => false,
+ _ => false,
+ };
+ if first_intermediate_is_questionmark {
+ match params.get(0) {
+ Some(&25) => {
+ self.grid.show_cursor();
+ self.mark_for_rerender();
+ }
+ Some(&1049) => {
+ let columns = self
+ .position_and_size_override
+ .map(|x| x.columns)
+ .unwrap_or(self.position_and_size.columns);
+ let rows = self
+ .position_and_size_override
+ .map(|x| x.rows)
+ .unwrap_or(self.position_and_size.rows);
+ let current_grid =
+ std::mem::replace(&mut self.grid, Grid::new(rows, columns));
+ self.alternative_grid = Some(current_grid);
+ self.clear_viewport_before_rendering = true;
+ }
+ Some(&1) => {
+ self.cursor_key_mode = true;
+ }
+ _ => {}
+ };
+ }
+ } else if c == 'r' {
+ if params.len() > 1 {
+ // minus 1 because these are 1 indexed
+ let top_line_index = params[0] as usize - 1;
+ let bottom_line_index = params[1] as usize - 1;
+ self.grid
+ .set_scroll_region(top_line_index, bottom_line_index);
+ self.grid.show_cursor();
+ } else {
+ self.grid.clear_scroll_region();
+ }
+ } else if c == 't' {
+ // TBD - title?
+ } else if c == 'n' {
+ // TBD - device status report
+ } else if c == 'c' {
+ // TBD - identify terminal
+ } else if c == 'M' {
+ // delete lines if currently inside scroll region
+ let line_count_to_delete = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.grid
+ .delete_lines_in_scroll_region(line_count_to_delete);
+ } else if c == 'L' {
+ // insert blank lines if inside scroll region
+ let line_count_to_add = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.grid
+ .add_empty_lines_in_scroll_region(line_count_to_add);
+ } else if c == 'q' {
+ // ignore for now to run on mac
+ } else if c == 'G' {
+ let column = if params[0] == 0 {
+ 0
+ } else {
+ params[0] as usize - 1
+ };
+ self.grid.move_cursor_to_column(column);
+ } else if c == 'd' {
+ // goto line
+ let line = if params[0] == 0 {
+ 1
+ } else {
+ // minus 1 because this is 1 indexed
+ params[0] as usize - 1
+ };
+ self.grid.move_cursor_to_line(line);
+ } else if c == 'P' {
+ // erase characters
+ let count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.grid.erase_characters(count, self.pending_styles);
+ } else if c == 'X' {
+ // erase characters and replace with empty characters of current style
+ let count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.grid
+ .replace_with_empty_chars(count, self.pending_styles);
+ } else if c == 'T' {
+ /*
+ * 124 54 T SD
+ * Scroll down, new lines inserted at top of screen
+ * [4T = Scroll down 4, bring previous lines back into view
+ */
+ let line_count: i64 = *params.get(0).expect("A number of lines was expected.");
+
+ if line_count >= 0 {
+ self.rotate_scroll_region_up(line_count as usize);
+ } else {
+ self.rotate_scroll_region_down(line_count.abs() as usize);
+ }
+ } else if c == 'S' {
+ // move scroll up
+ let count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.grid.delete_lines_in_scroll_region(count);
+ self.grid.add_empty_lines_in_scroll_region(count);
+ } else {
+ let _ = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params));
+ }
+ }
+
+ fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
+ if let (b'M', None) = (byte, intermediates.get(0)) {
+ self.grid.move_cursor_up_with_scrolling(1);
+ }
+ }
+}
diff --git a/src/client/tab.rs b/src/client/tab.rs
new file mode 100644
index 00000000..b7f0e5c2
--- /dev/null
+++ b/src/client/tab.rs
@@ -0,0 +1,2004 @@
+use crate::common::{AppInstruction, SenderWithContext};
+use crate::panes::{PaneId, PositionAndSize, TerminalPane};
+use crate::pty_bus::{PtyInstruction, VteEvent};
+use crate::{boundaries::Boundaries, panes::PluginPane};
+use crate::{layout::Layout, wasm_vm::PluginInstruction};
+use crate::{os_input_output::OsApi, utils::shared::pad_to_size};
+use std::os::unix::io::RawFd;
+use std::{
+ cmp::Reverse,
+ collections::{BTreeMap, HashSet},
+};
+use std::{io::Write, sync::mpsc::channel};
+
+use crate::utils::logging::debug_log_to_file;
+
+/*
+ * Tab
+ *
+ * this holds multiple panes (currently terminal panes) which are currently displayed
+ * when this tab is active.
+ * it tracks their coordinates (x/y) and size, as well as how they should be resized
+ *
+ */
+
+const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
+const MIN_TERMINAL_HEIGHT: usize = 2;
+const MIN_TERMINAL_WIDTH: usize = 4;
+
+type BorderAndPaneIds = (usize, Vec<PaneId>);
+
+fn split_vertically_with_gap(rect: &PositionAndSize) -> (PositionAndSize, PositionAndSize) {
+ let width_of_each_half = (rect.columns - 1) / 2;
+ let mut first_rect = *rect;
+ let mut second_rect = *rect;
+ if rect.columns % 2 == 0 {
+ first_rect.columns = width_of_each_half + 1;
+ } else {
+ first_rect.columns = width_of_each_half;
+ }
+ second_rect.x = first_rect.x + first_rect.columns + 1;
+ second_rect.columns = width_of_each_half;
+ (first_rect, second_rect)
+}
+
+fn split_horizontally_with_gap(rect: &PositionAndSize) -> (PositionAndSize, PositionAndSize) {
+ let height_of_each_half = (rect.rows - 1) / 2;
+ let mut first_rect = *rect;
+ let mut second_rect = *rect;
+ if rect.rows % 2 == 0 {
+ first_rect.rows = height_of_each_half + 1;
+ } else {
+ first_rect.rows = height_of_each_half;
+ }
+ second_rect.y = first_rect.y + first_rect.rows + 1;
+ second_rect.rows = height_of_each_half;
+ (first_rect, second_rect)
+}
+
+pub struct Tab {
+ pub index: usize,
+ panes: BTreeMap<PaneId, Box<dyn Pane>>,
+ panes_to_hide: HashSet<PaneId>,
+ active_terminal: Option<PaneId>,
+ max_panes: Option<usize>,
+ full_screen_ws: PositionAndSize,
+ fullscreen_is_active: bool,
+ os_api: Box<dyn OsApi>,
+ pub send_pty_instructions: SenderWithContext<PtyInstruction>,
+ pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ pub send_app_instructions: SenderWithContext<AppInstruction>,
+}
+
+// FIXME: Use a struct that has a pane_type enum, to reduce all of the duplication
+pub trait Pane {
+ fn x(&self) -> usize;
+ fn y(&self) -> usize;
+ fn rows(&self) -> usize;
+ fn columns(&self) -> usize;
+ fn reset_size_and_position_override(&mut self);
+ fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize);
+ fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize);
+ fn handle_event(&mut self, event: VteEvent);
+ fn cursor_coordinates(&self) -> Option<(usize, usize)>;
+ fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8>;
+
+ fn position_and_size_override(&self) -> Option<PositionAndSize>;
+ fn should_render(&self) -> bool;
+ fn set_should_render(&mut self, should_render: bool);
+ fn selectable(&self) -> bool;
+ fn set_selectable(&mut self, selectable: bool);
+ fn set_invisible_borders(&mut self, invisible_borders: bool);
+ fn set_max_height(&mut self, max_height: usize);
+ fn render(&mut self) -> Option<String>;
+ fn pid(&self) -> PaneId;
+ fn reduce_height_down(&mut self, count: usize);
+ fn increase_height_down(&mut self, count: usize);
+ fn increase_height_up(&mut self, count: usize);
+ fn reduce_height_up(&mut self, count: usize);
+ fn increase_width_right(&mut self, count: usize);
+ fn reduce_width_right(&mut self, count: usize);
+ fn reduce_width_left(&mut self, count: usize);
+ fn increase_width_left(&mut self, count: usize);
+ fn scroll_up(&mut self, count: usize);
+ fn scroll_down(&mut self, count: usize);
+ fn clear_scroll(&mut self);
+
+ fn right_boundary_x_coords(&self) -> usize {
+ self.x() + self.columns()
+ }
+ fn bottom_boundary_y_coords(&self) -> usize {
+ self.y() + self.rows()
+ }
+ fn is_directly_right_of(&self, other: &dyn Pane) -> bool {
+ self.x() == other.x() + other.columns() + 1
+ }
+ fn is_directly_left_of(&self, other: &dyn Pane) -> bool {
+ self.x() + self.columns() + 1 == other.x()
+ }
+ fn is_directly_below(&self, other: &dyn Pane) -> bool {
+ self.y() == other.y() + other.rows() + 1
+ }
+ fn is_directly_above(&self, other: &dyn Pane) -> bool {
+ self.y() + self.rows() + 1 == other.y()
+ }
+ fn horizontally_overlaps_with(&self, other: &dyn Pane) -> bool {
+ (self.y() >= other.y() && self.y() <= (other.y() + other.rows()))
+ || ((self.y() + self.rows()) <= (other.y() + other.rows())
+ && (self.y() + self.rows()) > other.y())
+ || (self.y() <= other.y() && (self.y() + self.rows() >= (other.y() + other.rows())))
+ || (other.y() <= self.y() && (other.y() + other.rows() >= (self.y() + self.rows())))
+ }
+ fn get_horizontal_overlap_with(&self, other: &dyn Pane) -> usize {
+ std::cmp::min(self.y() + self.rows(), other.y() + other.rows())
+ - std::cmp::max(self.y(), other.y())
+ }
+ fn vertically_overlaps_with(&self, other: &dyn Pane) -> bool {
+ (self.x() >= other.x() && self.x() <= (other.x() + other.columns()))
+ || ((self.x() + self.columns()) <= (other.x() + other.columns())
+ && (self.x() + self.columns()) > other.x())
+ || (self.x() <= other.x()
+ && (self.x() + self.columns() >= (other.x() + other.columns())))
+ || (other.x() <= self.x()
+ && (other.x() + other.columns() >= (self.x() + self.columns())))
+ }
+ fn get_vertical_overlap_with(&self, other: &dyn Pane) -> usize {
+ std::cmp::min(self.x() + self.columns(), other.x() + other.columns())
+ - std::cmp::max(self.x(), other.x())
+ }
+ fn min_width(&self) -> usize {
+ MIN_TERMINAL_WIDTH
+ }
+ fn min_height(&self) -> usize {
+ MIN_TERMINAL_HEIGHT
+ }
+ fn max_width(&self) -> Option<usize> {
+ None
+ }
+ fn max_height(&self) -> Option<usize> {
+ None
+ }
+ fn invisible_borders(&self) -> bool {
+ false
+ }
+}
+
+impl Tab {
+ // FIXME: Too many arguments here! Maybe bundle all of the senders for the whole program in a struct?
+ pub fn new(
+ index: usize,
+ full_screen_ws: &PositionAndSize,
+ mut os_api: Box<dyn OsApi>,
+ send_pty_instructions: SenderWithContext<PtyInstruction>,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ send_app_instructions: SenderWithContext<AppInstruction>,
+ max_panes: Option<usize>,
+ pane_id: Option<PaneId>,
+ ) -> Self {
+ let panes = if let Some(PaneId::Terminal(pid)) = pane_id {
+ let new_terminal = TerminalPane::new(pid, *full_screen_ws);
+ os_api.set_terminal_size_using_fd(
+ new_terminal.pid,
+ new_terminal.columns() as u16,
+ new_terminal.rows() as u16,
+ );
+ let mut panes: BTreeMap<PaneId, Box<dyn Pane>> = BTreeMap::new();
+ panes.insert(PaneId::Terminal(pid), Box::new(new_terminal));
+ panes
+ } else {
+ BTreeMap::new()
+ };
+ Tab {
+ index,
+ panes,
+ max_panes,
+ panes_to_hide: HashSet::new(),
+ active_terminal: pane_id,
+ full_screen_ws: *full_screen_ws,
+ fullscreen_is_active: false,
+ os_api,
+ send_app_instructions,
+ send_pty_instructions,
+ send_plugin_instructions,
+ }
+ }
+
+ pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>) {
+ // TODO: this should be an attribute on Screen instead of full_screen_ws
+ let free_space = PositionAndSize {
+ x: 0,
+ y: 0,
+ rows: self.full_screen_ws.rows,
+ columns: self.full_screen_ws.columns,
+ };
+ self.panes_to_hide.clear();
+ let positions_in_layout = layout.position_panes_in_space(&free_space);
+ let mut positions_and_size = positions_in_layout.iter();
+ for (pane_kind, terminal_pane) in self.panes.iter_mut() {
+ // for now the layout only supports terminal panes
+ if let PaneId::Terminal(pid) = pane_kind {
+ match positions_and_size.next() {
+ Some((_, position_and_size)) => {
+ terminal_pane.reset_size_and_position_override();
+ terminal_pane.change_pos_and_size(&position_and_size);
+ self.os_api.set_terminal_size_using_fd(
+ *pid,
+ position_and_size.columns as u16,
+ position_and_size.rows as u16,
+ );
+ }
+ None => {
+ // we filled the entire layout, no room for this pane
+ // TODO: handle active terminal
+ self.panes_to_hide.insert(PaneId::Terminal(*pid));
+ }
+ }
+ }
+ }
+ let mut new_pids = new_pids.iter();
+ for (layout, position_and_size) in positions_and_size {
+ // Just a regular terminal
+ if let Some(plugin) = &layout.plugin {
+ let (pid_tx, pid_rx) = channel();
+ self.send_plugin_instructions
+ .send(PluginInstruction::Load(pid_tx, plugin.clone()))
+ .unwrap();
+ let pid = pid_rx.recv().unwrap();
+ let new_plugin = PluginPane::new(
+ pid,
+ *position_and_size,
+ self.send_plugin_instructions.clone(),
+ );
+ self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin));
+ } else {
+ // there are still panes left to fill, use the pids we received in this method
+ let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout
+ let new_terminal = TerminalPane::new(*pid, *position_and_size);
+ self.os_api.set_terminal_size_using_fd(
+ new_terminal.pid,
+ new_terminal.columns() as u16,
+ new_terminal.rows() as u16,
+ );
+ self.panes
+ .insert(PaneId::Terminal(*pid), Box::new(new_terminal));
+ }
+ }
+ for unused_pid in new_pids {
+ // this is a bit of a hack and happens because we don't have any central location that
+ // can query the screen as to how many panes it needs to create a layout
+ // fixing this will require a bit of an architecture change
+ self.send_pty_instructions
+ .send(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
+ .unwrap();
+ }
+ self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next();
+ self.render();
+ }
+ pub fn new_pane(&mut self, pid: PaneId) {
+ self.close_down_to_max_terminals();
+ if self.fullscreen_is_active {
+ self.toggle_active_pane_fullscreen();
+ }
+ if !self.has_panes() {
+ // FIXME: This could use a second look
+ if let PaneId::Terminal(term_pid) = pid {
+ let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws);
+ self.os_api.set_terminal_size_using_fd(
+ new_terminal.pid,
+ new_terminal.columns() as u16,
+ new_terminal.rows() as u16,
+ );
+ self.panes.insert(pid, Box::new(new_terminal));
+ self.active_terminal = Some(pid);
+ }
+ } else {
+ // TODO: check minimum size of active terminal
+
+ let (_largest_terminal_size, terminal_id_to_split) = self.get_panes().fold(
+ (0, None),
+ |(current_largest_terminal_size, current_terminal_id_to_split),
+ id_and_terminal_to_check| {
+ let (id_of_terminal_to_check, terminal_to_check) = id_and_terminal_to_check;
+ let terminal_size = (terminal_to_check.rows() * CURSOR_HEIGHT_WIDTH_RATIO)
+ * terminal_to_check.columns();
+ let terminal_can_be_split = terminal_to_check.columns() >= MIN_TERMINAL_WIDTH
+ && terminal_to_check.rows() >= MIN_TERMINAL_HEIGHT
+ && ((terminal_to_check.columns() >= terminal_to_check.min_width() * 2 + 1)
+ || (terminal_to_check.rows()
+ >= terminal_to_check.min_height() * 2 + 1));
+ if terminal_can_be_split && terminal_size > current_largest_terminal_size {
+ (terminal_size, Some(*id_of_terminal_to_check))
+ } else {
+ (current_largest_terminal_size, current_terminal_id_to_split)
+ }
+ },
+ );
+ if terminal_id_to_split.is_none() {
+ self.send_pty_instructions
+ .send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
+ .unwrap();
+ return; // likely no terminal large enough to split
+ }
+ let terminal_id_to_split = terminal_id_to_split.unwrap();
+ let terminal_to_split = self.panes.get_mut(&terminal_id_to_split).unwrap();
+ let terminal_ws = PositionAndSize {
+ rows: terminal_to_split.rows(),
+ columns: terminal_to_split.columns(),
+ x: terminal_to_split.x(),
+ y: terminal_to_split.y(),
+ };
+ if terminal_to_split.rows() * CURSOR_HEIGHT_WIDTH_RATIO > terminal_to_split.columns()
+ && terminal_to_split.rows() >= terminal_to_split.min_height() * 2 + 1
+ {
+ // FIXME: This could use a second look
+ if let PaneId::Terminal(term_pid) = pid {
+ let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws);
+ let new_terminal = TerminalPane::new(term_pid, bottom_winsize);
+ self.os_api.set_terminal_size_using_fd(
+ new_terminal.pid,
+ bottom_winsize.columns as u16,
+ bottom_winsize.rows as u16,
+ );
+ terminal_to_split.change_pos_and_size(&top_winsize);
+ self.panes.insert(pid, Box::new(new_terminal));
+ if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split {
+ self.os_api.set_terminal_size_using_fd(
+ terminal_id_to_split,
+ top_winsize.columns as u16,
+ top_winsize.rows as u16,
+ );
+ }
+ self.active_terminal = Some(pid);
+ }
+ } else if terminal_to_split.columns() >= terminal_to_split.min_width() * 2 + 1 {
+ // FIXME: This could use a second look
+ if let PaneId::Terminal(term_pid) = pid {
+ let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws);
+ let new_terminal = TerminalPane::new(term_pid, right_winsize);
+ self.os_api.set_terminal_size_using_fd(
+ new_terminal.pid,
+ right_winsize.columns as u16,
+ right_winsize.rows as u16,
+ );
+ terminal_to_split.change_pos_and_size(&left_winsize);
+ self.panes.insert(pid, Box::new(new_terminal));
+ if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split {
+ self.os_api.set_terminal_size_using_fd(
+ terminal_id_to_split,
+ left_winsize.columns as u16,
+ left_winsize.rows as u16,
+ );
+ }
+ }
+ }
+ self.active_terminal = Some(pid);
+ self.render();
+ }
+ }
+ pub fn horizontal_split(&mut self, pid: PaneId) {
+ self.close_down_to_max_terminals();
+ if self.fullscreen_is_active {
+ self.toggle_active_pane_fullscreen();
+ }
+ if !self.has_panes() {
+ // FIXME: This could use a second look
+ if let PaneId::Terminal(term_pid) = pid {
+ let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws);
+ self.os_api.set_terminal_size_using_fd(
+ new_terminal.pid,
+ new_terminal.columns() as u16,
+ new_terminal.rows() as u16,
+ );
+ self.panes.insert(pid, Box::new(new_terminal));
+ self.active_terminal = Some(pid);
+ }
+ } else {
+ // FIXME: This could use a second look
+ if let PaneId::Terminal(term_pid) = pid {
+ // TODO: check minimum size of active terminal
+ let active_pane_id = &self.get_active_pane_id().unwrap();
+ let active_pane = self.panes.get_mut(active_pane_id).unwrap();
+ if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 {
+ self.send_pty_instructions
+ .send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
+ .unwrap();
+ return;
+ }
+ let terminal_ws = PositionAndSize {
+ x: active_pane.x(),
+ y: active_pane.y(),
+ rows: active_pane.rows(),
+ columns: active_pane.columns(),
+ };
+ let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws);
+
+ active_pane.change_pos_and_size(&top_winsize);
+
+ let new_terminal = TerminalPane::new(term_pid, bottom_winsize);
+ self.os_api.set_terminal_size_using_fd(
+ new_terminal.pid,
+ bottom_winsize.columns as u16,
+ bottom_winsize.rows as u16,
+ );
+ self.panes.insert(pid, Box::new(new_terminal));
+
+ if let PaneId::Terminal(active_terminal_pid) = active_pane_id {
+ self.os_api.set_terminal_size_using_fd(
+ *active_terminal_pid,
+ top_winsize.columns as u16,
+ top_winsize.rows as u16,
+ );
+ }
+
+ self.active_terminal = Some(pid);
+ self.render();
+ }
+ }
+ }
+ pub fn vertical_split(&mut self, pid: PaneId) {
+ self.close_down_to_max_terminals();
+ if self.fullscreen_is_active {
+ self.toggle_active_pane_fullscreen();
+ }
+ if !self.has_panes() {
+ // FIXME: This could use a second look
+ if let PaneId::Terminal(term_pid) = pid {
+ let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws);
+ self.os_api.set_terminal_size_using_fd(
+ new_terminal.pid,
+ new_terminal.columns() as u16,
+ new_terminal.rows() as u16,
+ );
+ self.panes.insert(pid, Box::new(new_terminal));
+ self.active_terminal = Some(pid);
+ }
+ } else {
+ // FIXME: This could use a second look
+ if let PaneId::Terminal(term_pid) = pid {
+ // TODO: check minimum size of active terminal
+ let active_pane_id = &self.get_active_pane_id().unwrap();
+ let active_pane = self.panes.get_mut(active_pane_id).unwrap();
+ if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 {
+ self.send_pty_instructions
+ .send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
+ .unwrap();
+ return;
+ }
+ let terminal_ws = PositionAndSize {
+ x: active_pane.x(),
+ y: active_pane.y(),
+ rows: active_pane.rows(),
+ columns: active_pane.columns(),
+ };
+ let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws);
+
+ active_pane.change_pos_and_size(&left_winsize);
+
+ let new_terminal = TerminalPane::new(term_pid, right_winsize);
+ self.os_api.set_terminal_size_using_fd(
+ new_terminal.pid,
+ right_winsize.columns as u16,
+ right_winsize.rows as u16,
+ );
+ self.panes.insert(pid, Box::new(new_terminal));
+
+ if let PaneId::Terminal(active_terminal_pid) = active_pane_id {
+ self.os_api.set_terminal_size_using_fd(
+ *active_terminal_pid,
+ left_winsize.columns as u16,
+ left_winsize.rows as u16,
+ );
+ }
+
+ self.active_terminal = Some(pid);
+ self.render();
+ }
+ }
+ }
+ pub fn get_active_pane(&self) -> Option<&dyn Pane> {
+ // FIXME: Could use Option::map() here
+ match self.get_active_pane_id() {
+ Some(active_pane) => self.panes.get(&active_pane).map(Box::as_ref),
+ None => None,
+ }
+ }
+ fn get_active_pane_id(&self) -> Option<PaneId> {
+ self.active_terminal
+ }
+ fn get_active_terminal_id(&self) -> Option<RawFd> {
+ // FIXME: Is there a better way to do this?
+ if let Some(PaneId::Terminal(pid)) = self.active_terminal {
+ Some(pid)
+ } else {
+ None
+ }
+ }
+ pub fn handle_pty_event(&mut self, pid: RawFd, event: VteEvent) {
+ // if we don't have the terminal in self.terminals it's probably because
+ // of a race condition where the terminal was created in pty_bus but has not
+ // yet been created in Screen. These events are currently not buffered, so
+ // if you're debugging seemingly randomly missing stdout data, this is
+ // the reason
+ if let Some(terminal_output) = self.panes.get_mut(&PaneId::Terminal(pid)) {
+ terminal_output.handle_event(event);
+ }
+ }
+ pub fn write_to_active_terminal(&mut self, input_bytes: Vec<u8>) {
+ match self.get_active_pane_id() {
+ Some(PaneId::Terminal(active_terminal_id)) => {
+ let active_terminal = self.get_active_pane().unwrap();
+ let mut adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes);
+ self.os_api
+ .write_to_tty_stdin(active_terminal_id, &mut adjusted_input)
+ .expect("failed to write to terminal");
+ self.os_api
+ .tcdrain(active_terminal_id)
+ .expect("failed to drain terminal");
+ }
+ Some(PaneId::Plugin(pid)) => {
+ self.send_plugin_instructions
+ .send(PluginInstruction::Input(pid, input_bytes))
+ .unwrap();
+ }
+ _ => {}
+ }
+ }
+ pub fn get_active_terminal_cursor_position(&self) -> Option<(usize, usize)> {
+ // (x, y)
+ let active_terminal = &self.get_active_pane()?;
+ active_terminal
+ .cursor_coordinates()
+ .map(|(x_in_terminal, y_in_terminal)| {
+ let x = active_terminal.x() + x_in_terminal;
+ let y = active_terminal.y() + y_in_terminal;
+ (x, y)
+ })
+ }
+ pub fn toggle_active_pane_fullscreen(&mut self) {
+ if let Some(active_pane_id) = self.get_active_pane_id() {
+ if self
+ .get_active_pane()
+ .unwrap()
+ .position_and_size_override()
+ .is_some()
+ {
+ for terminal_id in self.panes_to_hide.iter() {
+ self.panes
+ .get_mut(terminal_id)
+ .unwrap()
+ .set_should_render(true);
+ }
+ self.panes_to_hide.clear();
+ let active_terminal = self.panes.get_mut(&active_pane_id).unwrap();
+ active_terminal.reset_size_and_position_override();
+ } else {
+ let terminals = self.get_panes();
+ let all_ids_except_current =
+ terminals.filter_map(
+ |(&id, _)| {
+ if id != active_pane_id {
+ Some(id)
+ } else {
+ None
+ }
+ },
+ );
+ self.panes_to_hide = all_ids_except_current.collect();
+ let active_terminal = self.panes.get_mut(&active_pane_id).unwrap();
+ active_terminal.override_size_and_position(0, 0, &self.full_screen_ws);
+ }
+ let active_terminal = self.panes.get(&active_pane_id).unwrap();
+ if let PaneId::Terminal(active_pid) = active_pane_id {
+ self.os_api.set_terminal_size_using_fd(
+ active_pid,
+ active_terminal.columns() as u16,
+ active_terminal.rows() as u16,
+ );
+ }
+ self.render();
+ self.toggle_fullscreen_is_active();
+ }
+ }
+ pub fn toggle_fullscreen_is_active(&mut self) {
+ self.fullscreen_is_active = !self.fullscreen_is_active;
+ }
+ pub fn render(&mut self) {
+ if self.active_terminal.is_none() {
+ // we might not have an active terminal if we closed the last pane
+ // in that case, we should not render as the app is exiting
+ return;
+ }
+ let mut stdout = self.os_api.get_stdout_writer();
+ let mut boundaries = Boundaries::new(
+ self.full_screen_ws.columns as u16,
+ self.full_screen_ws.rows as u16,
+ );
+ for (kind, terminal) in self.panes.iter_mut() {
+ if !self.panes_to_hide.contains(&terminal.pid()) {
+ boundaries.add_rect(terminal.as_ref());
+ if let Some(vte_output) = terminal.render() {
+ let vte_output = if let PaneId::Terminal(_) = kind {
+ vte_output
+ } else {
+ pad_to_size(&vte_output, terminal.rows(), terminal.columns())
+ };
+ // FIXME: Use Termion for cursor and style clearing?
+ write!(
+ stdout,
+ "\u{1b}[{};{}H\u{1b}[m{}",
+ terminal.y() + 1,
+ terminal.x() + 1,
+ vte_output
+ )
+ .expect("cannot write to stdout");
+ }
+ }
+ }
+
+ // TODO: only render (and calculate) boundaries if there was a resize
+ let vte_output = boundaries.vte_output();
+ stdout
+ .write_all(&vte_output.as_bytes())
+ .expect("cannot write to stdout");
+
+ match self.get_active_terminal_cursor_position() {
+ Some((cursor_position_x, cursor_position_y)) => {
+ let show_cursor = "\u{1b}[?25h";
+ let goto_cursor_position = format!(
+ "\u{1b}[{};{}H\u{1b}[m",
+ cursor_position_y + 1,
+ cursor_position_x + 1
+ ); // goto row/col
+ stdout
+ .write_all(&show_cursor.as_bytes())
+ .expect("cannot write to stdout");
+ stdout
+ .write_all(&goto_cursor_position.as_bytes())
+ .expect("cannot write to stdout");
+ stdout.flush().expect("could not flush");
+ }
+ None => {
+ let hide_cursor = "\u{1b}[?25l";
+ stdout
+ .write_all(&hide_cursor.as_bytes())
+ .expect("cannot write to stdout");
+ stdout.flush().expect("could not flush");
+ }
+ }
+ }
+ fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
+ self.panes.iter()
+ }
+ // FIXME: This is some shameful duplication...
+ fn get_selectable_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
+ self.panes.iter().filter(|(_, p)| p.selectable())
+ }
+ fn has_panes(&self) -> bool {
+ let mut all_terminals = self.get_panes();
+ all_terminals.next().is_some()
+ }
+ fn has_selectable_panes(&self) -> bool {
+ let mut all_terminals = self.get_selectable_panes();
+ all_terminals.next().is_some()
+ }
+ fn next_active_pane(&self, panes: Vec<PaneId>) -> Option<PaneId> {
+ panes
+ .into_iter()
+ .rev()
+ .find(|pid| self.panes.get(pid).unwrap().selectable())
+ }
+ fn pane_ids_directly_left_of(&self, id: &PaneId) -> Option<Vec<PaneId>> {
+ let mut ids = vec![];
+ let terminal_to_check = self.panes.get(id).unwrap();
+ if terminal_to_check.x() == 0 {
+ return None;
+ }
+ for (&pid, terminal) in self.get_panes() {
+ if terminal.x() + terminal.columns() == terminal_to_check.x() - 1 {
+ ids.push(pid);
+ }
+ }
+ if ids.is_empty() {
+ None
+ } else {
+ Some(ids)
+ }
+ }
+ fn pane_ids_directly_right_of(&self, id: &PaneId) -> Option<Vec<PaneId>> {
+ let mut ids = vec![];
+ let terminal_to_check = self.panes.get(id).unwrap();
+ for (&pid, terminal) in self.get_panes() {
+ if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() + 1 {
+ ids.push(pid);
+ }
+ }
+ if ids.is_empty() {
+ None
+ } else {
+ Some(ids)
+ }
+ }
+ fn pane_ids_directly_below(&self, id: &PaneId) -> Option<Vec<PaneId>> {
+ let mut ids = vec![];
+ let terminal_to_check = self.panes.get(id).unwrap();
+ for (&pid, terminal) in self.get_panes() {
+ if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() + 1 {
+ ids.push(pid);
+ }
+ }
+ if ids.is_empty() {
+ None
+ } else {
+ Some(ids)
+ }
+ }
+ fn pane_ids_directly_above(&self, id: &PaneId) -> Option<Vec<PaneId>> {
+ let mut ids = vec![];
+ let terminal_to_check = self.panes.get(id).unwrap();
+ for (&pid, terminal) in self.get_panes() {
+ if terminal.y() + terminal.rows() + 1 == terminal_to_check.y() {
+ ids.push(pid);
+ }
+ }
+ if ids.is_empty() {
+ None
+ } else {
+ Some(ids)
+ }
+ }
+ fn panes_top_aligned_with_pane(&self, pane: &dyn Pane) -> Vec<&dyn Pane> {
+ self.panes
+ .keys()
+ .map(|t_id| self.panes.get(&t_id).unwrap().as_ref())
+ .filter(|terminal| terminal.pid() != pane.pid() && terminal.y() == pane.y())
+ .collect()
+ }
+ fn panes_bottom_aligned_with_pane(&self, pane: &dyn Pane) -> Vec<&dyn Pane> {
+ self.panes
+ .keys()
+ .map(|t_id| self.panes.get(&t_id).unwrap().as_ref())
+ .filter(|terminal| {
+ terminal.pid() != pane.pid()
+ && terminal.y() + terminal.rows() == pane.y() + pane.rows()
+ })
+ .collect()
+ }
+ fn panes_right_aligned_with_pane(&self, pane: &dyn Pane) -> Vec<&dyn Pane> {
+ self.panes
+ .keys()
+ .map(|t_id| self.panes.get(&t_id).unwrap().as_ref())
+ .filter(|terminal| {
+ terminal.pid() != pane.pid()
+ && terminal.x() + terminal.columns() == pane.x() + pane.columns()
+ })
+ .collect()
+ }
+ fn panes_left_aligned_with_pane(&self, pane: &dyn Pane) -> Vec<&dyn Pane> {
+ self.panes
+ .keys()
+ .map(|t_id| self.panes.get(&t_id).unwrap().as_ref())
+ .filter(|terminal| terminal.pid() != pane.pid() && terminal.x() == pane.x())
+ .collect()
+ }
+ fn right_aligned_contiguous_panes_above(
+ &self,
+ id: &PaneId,
+ terminal_borders_to_the_right: &HashSet<usize>,
+ ) -> BorderAndPaneIds {
+ let mut terminals = vec![];
+ let terminal_to_check = self
+ .panes
+ .get(id)
+ .expect("terminal id does not exist")
+ .as_ref();
+ let mut right_aligned_terminals = self.panes_right_aligned_with_pane(terminal_to_check);
+ // terminals that are next to each other up to current
+ right_aligned_terminals.sort_by_key(|a| Reverse(a.y()));
+ for terminal in right_aligned_terminals {
+ let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check);
+ if terminal.y() + terminal.rows() + 1 == terminal_to_check.y() {
+ terminals.push(terminal);
+ }
+ }
+ // top-most border aligned with a pane border to the right
+ let mut top_resize_border = 0;
+ for terminal in &terminals {
+ let bottom_terminal_boundary = terminal.y() + terminal.rows();
+ if terminal_borders_to_the_right
+ .get(&(bottom_terminal_boundary + 1))
+ .is_some()
+ && top_resize_border < bottom_terminal_boundary
+ {
+ top_resize_border = bottom_terminal_boundary + 1;
+ }
+ }
+ terminals.retain(|terminal| terminal.y() >= top_resize_border);
+ // if there are no adjacent panes to resize, we use the border of the main pane we're
+ // resizing
+ let top_resize_border = if terminals.is_empty() {
+ terminal_to_check.y()
+ } else {
+ top_resize_border
+ };
+ let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect();
+ (top_resize_border, terminal_ids)
+ }
+ fn right_aligned_contiguous_panes_below(
+ &self,
+ id: &PaneId,
+ terminal_borders_to_the_right: &HashSet<usize>,
+ ) -> BorderAndPaneIds {
+ let mut terminals = vec![];
+ let terminal_to_check = self
+ .panes
+ .get(id)
+ .expect("terminal id does not exist")
+ .as_ref();
+ let mut right_aligned_terminals = self.panes_right_aligned_with_pane(terminal_to_check);
+ // terminals that are next to each other up to current
+ right_aligned_terminals.sort_by_key(|a| a.y());
+ for terminal in right_aligned_terminals {
+ let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check);
+ if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() + 1 {
+ terminals.push(terminal);
+ }
+ }
+ // bottom-most border aligned with a pane border to the right
+ let mut bottom_resize_border = self.full_screen_ws.rows;
+ for terminal in &terminals {
+ let top_terminal_boundary = terminal.y();
+ if terminal_borders_to_the_right
+ .get(&(top_terminal_boundary))
+ .is_some()
+ && top_terminal_boundary < bottom_resize_border
+ {
+ bottom_resize_border = top_terminal_boundary;
+ }
+ }
+ terminals.retain(|terminal| terminal.y() + terminal.rows() <= bottom_resize_border);
+ // if there are no adjacent panes to resize, we use the border of the main pane we're
+ // resizing
+ let bottom_resize_border = if terminals.is_empty() {
+ terminal_to_check.y() + terminal_to_check.rows()
+ } else {
+ bottom_resize_border
+ };
+ let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect();
+ (bottom_resize_border, terminal_ids)
+ }
+ fn left_aligned_contiguous_panes_above(
+ &self,
+ id: &PaneId,
+ terminal_borders_to_the_left: &HashSet<usize>,
+ ) -> BorderAndPaneIds {
+ let mut terminals = vec![];
+ let terminal_to_check = self
+ .panes
+ .get(id)
+ .expect("terminal id does not exist")
+ .as_ref();
+ let mut left_aligned_terminals = self.panes_left_aligned_with_pane(terminal_to_check);
+ // terminals that are next to each other up to current
+ left_aligned_terminals.sort_by_key(|a| Reverse(a.y()));
+ for terminal in left_aligned_terminals {
+ let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check);
+ if terminal.y() + terminal.rows() + 1 == terminal_to_check.y() {
+ terminals.push(terminal);
+ }
+ }
+ // top-most border aligned with a pane border to the right
+ let mut top_resize_border = 0;
+ for terminal in &terminals {
+ let bottom_terminal_boundary = terminal.y() + terminal.rows();
+ if terminal_borders_to_the_left
+ .get(&(bottom_terminal_boundary + 1))
+ .is_some()
+ && top_resize_border < bottom_terminal_boundary
+ {
+ top_resize_border = bottom_terminal_boundary + 1;
+ }
+ }
+ terminals.retain(|terminal| terminal.y() >= top_resize_border);
+ // if there are no adjacent panes to resize, we use the border of the main pane we're
+ // resizing
+ let top_resize_border = if terminals.is_empty() {
+ terminal_to_check.y()
+ } else {
+ top_resize_border
+ };
+ let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect();
+ (top_resize_border, terminal_ids)
+ }
+ fn left_aligned_contiguous_panes_below(
+ &self,
+ id: &PaneId,
+ terminal_borders_to_the_left: &HashSet<usize>,
+ ) -> BorderAndPaneIds {
+ let mut terminals = vec![];
+ let terminal_to_check = self
+ .panes
+ .get(id)
+ .expect("terminal id does not exist")
+ .as_ref();
+ let mut left_aligned_terminals = self.panes_left_aligned_with_pane(terminal_to_check);
+ // terminals that are next to each other up to current
+ left_aligned_terminals.sort_by_key(|a| a.y());
+ for terminal in left_aligned_terminals {
+ let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check);
+ if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() + 1 {
+ terminals.push(terminal);
+ }
+ }
+ // bottom-most border aligned with a pane border to the left
+ let mut bottom_resize_border = self.full_screen_ws.rows;
+ for terminal in &terminals {
+ let top_terminal_boundary = terminal.y();
+ if terminal_borders_to_the_left
+ .get(&(top_terminal_boundary))
+ .is_some()
+ && top_terminal_boundary < bottom_resize_border
+ {
+ bottom_resize_border = top_terminal_boundary;
+ }
+ }
+ terminals.retain(|terminal| {
+ // terminal.y() + terminal.rows() < bottom_resize_border
+ terminal.y() + terminal.rows() <= bottom_resize_border
+ });
+ // if there are no adjacent panes to resize, we use the border of the main pane we're
+ // resizing
+ let bottom_resize_border = if terminals.is_empty() {
+ terminal_to_check.y() + terminal_to_check.rows()
+ } else {
+ bottom_resize_border
+ };
+ let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect();
+ (bottom_resize_border, terminal_ids)
+ }
+ fn top_aligned_contiguous_panes_to_the_left(
+ &self,
+ id: &PaneId,
+ terminal_borders_above: &HashSet<usize>,
+ ) -> BorderAndPaneIds {
+ let mut terminals = vec![];
+ let terminal_to_check = self
+ .panes
+ .get(id)
+ .expect("terminal id does not exist")
+ .as_ref();
+ let mut top_aligned_terminals = self.panes_top_aligned_with_pane(terminal_to_check);
+ // terminals that are next to each other up to current
+ top_aligned_terminals.sort_by_key(|a| Reverse(a.x()));
+ for terminal in top_aligned_terminals {
+ let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check);
+ if terminal.x() + terminal.columns() + 1 == terminal_to_check.x() {
+ terminals.push(terminal);
+ }
+ }
+ // leftmost border aligned with a pane border above
+ let mut left_resize_border = 0;
+ for terminal in &terminals {
+ let right_terminal_boundary = terminal.x() + terminal.columns();
+ if terminal_borders_above
+ .get(&(right_terminal_boundary + 1))
+ .is_some()
+ && left_resize_border < right_terminal_boundary
+ {
+ left_resize_border = right_terminal_boundary + 1;
+ }
+ }
+ terminals.retain(|terminal| terminal.x() >= left_resize_border);
+ // if there are no adjacent panes to resize, we use the border of the main pane we're
+ // resizing
+ let left_resize_border = if terminals.is_empty() {
+ terminal_to_check.x()
+ } else {
+ left_resize_border
+ };
+ let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect();
+ (left_resize_border, terminal_ids)
+ }
+ fn top_aligned_contiguous_panes_to_the_right(
+ &self,
+ id: &PaneId,
+ terminal_borders_above: &HashSet<usize>,
+ ) -> BorderAndPaneIds {
+ let mut terminals = vec![];
+ let terminal_to_check = self.panes.get(id).unwrap().as_ref();
+ let mut top_aligned_terminals = self.panes_top_aligned_with_pane(terminal_to_check);
+ // terminals that are next to each other up to current
+ top_aligned_terminals.sort_by_key(|a| a.x());
+ for terminal in top_aligned_terminals {
+ let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check);
+ if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() + 1 {
+ terminals.push(terminal);
+ }
+ }
+ // rightmost border aligned with a pane border above
+ let mut right_resize_border = self.full_screen_ws.columns;
+ for terminal in &terminals {
+ let left_terminal_boundary = terminal.x();
+ if terminal_borders_above
+ .get(&left_terminal_boundary)
+ .is_some()
+ && right_resize_border > left_terminal_boundary
+ {
+ right_resize_border = left_terminal_boundary;
+ }
+ }
+ terminals.retain(|terminal| terminal.x() + terminal.columns() <= right_resize_border);
+ // if there are no adjacent panes to resize, we use the border of the main pane we're
+ // resizing
+ let right_resize_border = if terminals.is_empty() {
+ terminal_to_check.x() + terminal_to_check.columns()
+ } else {
+ right_resize_border
+ };
+ let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect();
+ (right_resize_border, terminal_ids)
+ }
+ fn bottom_aligned_contiguous_panes_to_the_left(
+ &self,
+ id: &PaneId,
+ terminal_borders_below: &HashSet<usize>,
+ ) -> BorderAndPaneIds {
+ let mut terminals = vec![];
+ let terminal_to_check = self.panes.get(id).unwrap().as_ref();
+ let mut bottom_aligned_terminals = self.panes_bottom_aligned_with_pane(terminal_to_check);
+ bottom_aligned_terminals.sort_by_key(|a| Reverse(a.x()));
+ // terminals that are next to each other up to current
+ for terminal in bottom_aligned_terminals {
+ let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check);
+ if terminal.x() + terminal.columns() + 1 == terminal_to_check.x() {
+ terminals.push(terminal);
+ }
+ }
+ // leftmost border aligned with a pane border above
+ let mut left_resize_border = 0;
+ for terminal in &terminals {
+ let right_terminal_boundary = terminal.x() + terminal.columns();
+ if terminal_borders_below
+ .get(&(right_terminal_boundary + 1))
+ .is_some()
+ && left_resize_border < right_terminal_boundary
+ {
+ left_resize_border = right_terminal_boundary + 1;
+ }
+ }
+ terminals.retain(|terminal| terminal.x() >= left_resize_border);
+ // if there are no adjacent panes to resize, we use the border of the main pane we're
+ // resizing
+ let left_resize_border = if terminals.is_empty() {
+ terminal_to_check.x()
+ } else {
+ left_resize_border
+ };
+ let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect();
+ (left_resize_border, terminal_ids)
+ }
+ fn bottom_aligned_contiguous_panes_to_the_right(
+ &self,
+ id: &PaneId,
+ terminal_borders_below: &HashSet<usize>,
+ ) -> BorderAndPaneIds {
+ let mut terminals = vec![];
+ let terminal_to_check = self.panes.get(id).unwrap().as_ref();
+ let mut bottom_aligned_terminals = self.panes_bottom_aligned_with_pane(terminal_to_check);
+ bottom_aligned_terminals.sort_by_key(|a| a.x());
+ // terminals that are next to each other up to current
+ for terminal in bottom_aligned_terminals {
+ let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check);
+ if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() + 1 {
+ terminals.push(terminal);
+ }
+ }
+ // leftmost border aligned with a pane border above
+ let mut right_resize_border = self.full_screen_ws.columns;
+ for terminal in &terminals {
+ let left_terminal_boundary = terminal.x();
+ if terminal_borders_below
+ .get(&left_terminal_boundary)
+ .is_some()
+ && right_resize_border > left_terminal_boundary
+ {
+ right_resize_border = left_terminal_boundary;
+ }
+ }
+ terminals.retain(|terminal| terminal.x() + terminal.columns() <= right_resize_border);
+ let right_resize_border = if terminals.is_empty() {
+ terminal_to_check.x() + terminal_to_check.columns()
+ } else {
+ right_resize_border
+ };
+ let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect();
+ (right_resize_border, terminal_ids)
+ }
+ fn reduce_pane_height_down(&mut self, id: &PaneId, count: usize) {
+ let terminal = self.panes.get_mut(id).unwrap();
+ terminal.reduce_height_down(count);
+ if let PaneId::Terminal(pid) = id {
+ self.os_api.set_terminal_size_using_fd(
+ *pid,
+ terminal.columns() as u16,
+ terminal.rows() as u16,
+ );
+ }
+ }
+ fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) {
+ let terminal = self.panes.get_mut(id).unwrap();
+ terminal.reduce_height_up(count);
+ if let PaneId::Terminal(pid) = id {
+ self.os_api.set_terminal_size_using_fd(
+ *pid,
+ terminal.columns() as u16,
+ terminal.rows() as u16,
+ );
+ }
+ }
+ fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) {
+ let terminal = self.panes.get_mut(id).unwrap();
+ terminal.increase_height_down(count);
+ if let PaneId::Terminal(pid) = terminal.pid() {
+ self.os_api.set_terminal_size_using_fd(
+ pid,
+ terminal.columns() as u16,
+ terminal.rows() as u16,
+ );
+ }
+ }
+ fn increase_pane_height_up(&mut self, id: &PaneId, count: usize) {
+ let terminal = self.panes.get_mut(id).unwrap();
+ terminal.increase_height_up(count);
+ if let PaneId::Terminal(pid) = terminal.pid() {
+ self.os_api.set_terminal_size_using_fd(
+ pid,
+ terminal.columns() as u16,
+ terminal.rows() as u16,
+ );
+ }
+ }
+ fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) {
+ let terminal = self.panes.get_mut(id).unwrap();
+ terminal.increase_width_right(count);
+ if let PaneId::Terminal(pid) = terminal.pid() {
+ self.os_api.set_terminal_size_using_fd(
+ pid,
+ terminal.columns() as u16,
+ terminal.rows() as u16,
+ );
+ }
+ }
+ fn increase_pane_width_left(&mut self, id: &PaneId, count: usize) {
+ let terminal = self.panes.get_mut(id).unwrap();
+ terminal.increase_width_left(count);
+ if let PaneId::Terminal(pid) = terminal.pid() {
+ self.os_api.set_terminal_size_using_fd(
+ pid,
+ terminal.columns() as u16,
+ terminal.rows() as u16,
+ );
+ }
+ }
+ fn reduce_pane_width_right(&mut self, id: &PaneId, count: usize) {
+ let terminal = self.panes.get_mut(id).unwrap();
+ terminal.reduce_width_right(count);
+ if let PaneId::Terminal(pid) = terminal.pid() {
+ self.os_api.set_terminal_size_using_fd(
+ pid,
+ terminal.columns() as u16,
+ terminal.rows() as u16,
+ );
+ }
+ }
+ fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) {
+ let terminal = self.panes.get_mut(id).unwrap();
+ terminal.reduce_width_left(count);
+ if let PaneId::Terminal(pid) = terminal.pid() {
+ self.os_api.set_terminal_size_using_fd(
+ pid,
+ terminal.columns() as u16,
+ terminal.rows() as u16,
+ );
+ }
+ }
+ fn pane_is_between_vertical_borders(
+ &self,
+ id: &PaneId,
+ left_border_x: usize,
+ right_border_x: usize,
+ ) -> bool {
+ let terminal = self
+ .panes
+ .get(id)
+ .expect("could not find terminal to check between borders");
+ terminal.x() >= left_border_x && terminal.x() + terminal.columns() <= right_border_x
+ }
+ fn pane_is_between_horizontal_borders(
+ &self,
+ id: &PaneId,
+ top_border_y: usize,
+ bottom_border_y: usize,
+ ) -> bool {
+ let terminal = self
+ .panes
+ .get(id)
+ .expect("could not find terminal to check between borders");
+ terminal.y() >= top_border_y && terminal.y() + terminal.rows() <= bottom_border_y
+ }
+ fn reduce_pane_and_surroundings_up(&mut self, id: &PaneId, count: usize) {
+ let mut terminals_below = self
+ .pane_ids_directly_below(&id)
+ .expect("can't reduce pane size up if there are no terminals below");
+ let terminal_borders_below: HashSet<usize> = terminals_below
+ .iter()
+ .map(|t| self.panes.get(t).unwrap().x())
+ .collect();
+ let (left_resize_border, terminals_to_the_left) =
+ self.bottom_aligned_contiguous_panes_to_the_left(&id, &terminal_borders_below);
+ let (right_resize_border, terminals_to_the_right) =
+ self.bottom_aligned_contiguous_panes_to_the_right(&id, &terminal_borders_below);
+ terminals_below.retain(|t| {
+ self.pane_is_between_vertical_borders(t, left_resize_border, right_resize_border)
+ });
+ self.reduce_pane_height_up(&id, count);
+ for terminal_id in terminals_below {
+ self.increase_pane_height_up(&terminal_id, count);
+ }
+ for terminal_id in terminals_to_the_left
+ .iter()
+ .chain(terminals_to_the_right.iter())
+ {
+ self.reduce_pane_height_up(&terminal_id, count);
+ }
+ }
+ fn reduce_pane_and_surroundings_down(&mut self, id: &PaneId, count: usize) {
+ let mut terminals_above = self
+ .pane_ids_directly_above(&id)
+ .expect("can't reduce pane size down if there are no terminals above");
+ let terminal_borders_above: HashSet<usize> = terminals_above
+ .iter()
+ .map(|t| self.panes.get(t).unwrap().x())
+ .collect();
+ let (left_resize_border, terminals_to_the_left) =
+ self.top_aligned_contiguous_panes_to_the_left(&id, &terminal_borders_above);
+ let (right_resize_border, terminals_to_the_right) =
+ self.top_aligned_contiguous_panes_to_the_right(&id, &terminal_borders_above);
+ terminals_above.retain(|t| {
+ self.pane_is_between_vertical_borders(t, left_resize_border, right_resize_border)
+ });
+ self.reduce_pane_height_down(&id, count);
+ for terminal_id in terminals_above {
+ self.increase_pane_height_down(&terminal_id, count);
+ }
+ for terminal_id in terminals_to_the_left
+ .iter()
+ .chain(terminals_to_the_right.iter())
+ {
+ self.reduce_pane_height_down(&terminal_id, count);
+ }
+ }
+ fn reduce_pane_and_surroundings_right(&mut self, id: &PaneId, count: usize) {
+ let mut terminals_to_the_left = self
+ .pane_ids_directly_left_of(&id)
+ .expect("can't reduce pane size right if there are no terminals to the left");
+ let terminal_borders_to_the_left: HashSet<usize> = terminals_to_the_left
+ .iter()
+ .map(|t| self.panes.get(t).unwrap().y())
+ .collect();
+ let (top_resize_border, terminals_above) =
+ self.left_aligned_contiguous_panes_above(&id, &terminal_borders_to_the_left);
+ let (bottom_resize_border, terminals_below) =
+ self.left_aligned_contiguous_panes_below(&id, &terminal_borders_to_the_left);
+ terminals_to_the_left.retain(|t| {
+ self.pane_is_between_horizontal_borders(t, top_resize_border, bottom_resize_border)
+ });
+ self.reduce_pane_width_right(&id, count);
+ for terminal_id in terminals_to_the_left {
+ self.increase_pane_width_right(&terminal_id, count);
+ }
+ for terminal_id in terminals_above.iter().chain(terminals_below.iter()) {
+ self.reduce_pane_width_right(&terminal_id, count);
+ }
+ }
+ fn reduce_pane_and_surroundings_left(&mut self, id: &PaneId, count: usize) {
+ let mut terminals_to_the_right = self
+ .pane_ids_directly_right_of(&id)
+ .expect("can't reduce pane size left if there are no terminals to the right");
+ let terminal_borders_to_the_right: HashSet<usize> = terminals_to_the_right
+ .iter()
+ .map(|t| self.panes.get(t).unwrap().y())
+ .collect();
+ let (top_resize_border, terminals_above) =
+ self.right_aligned_contiguous_panes_above(&id, &terminal_borders_to_the_right);
+ let (bottom_resize_border, terminals_below) =
+ self.right_aligned_contiguous_panes_below(&id, &terminal_borders_to_the_right);
+ terminals_to_the_right.retain(|t| {
+ self.pane_is_between_horizontal_borders(t, top_resize_border, bottom_resize_border)
+ });
+ self.reduce_pane_width_left(&id, count);
+ for terminal_id in terminals_to_the_right {
+ self.increase_pane_width_left(&terminal_id, count);
+ }
+ for terminal_id in terminals_above.iter().chain(terminals_below.iter()) {
+ self.reduce_pane_width_left(&terminal_id, count);
+ }
+ }
+ fn increase_pane_and_surroundings_up(&mut self, id: &PaneId, count: usize) {
+ let mut terminals_above = self
+ .pane_ids_directly_above(&id)
+ .expect("can't increase pane size up if there are no terminals above");
+ let terminal_borders_above: HashSet<usize> = terminals_above
+ .iter()
+ .map(|t| self.panes.get(t).unwrap().x())
+ .collect();
+ let (left_resize_border, terminals_to_the_left) =
+ self.top_aligned_contiguous_panes_to_the_left(&id, &terminal_borders_above);
+ let (right_resize_border, terminals_to_the_right) =
+ self.top_aligned_contiguous_panes_to_the_right(&id, &terminal_borders_above);
+ terminals_above.retain(|t| {
+ self.pane_is_between_vertical_borders(t, left_resize_border, right_resize_border)
+ });
+ self.increase_pane_height_up(&id, count);
+ for terminal_id in terminals_above {
+ self.reduce_pane_height_up(&terminal_id, count);
+ }
+ for terminal_id in terminals_to_the_left
+ .iter()
+ .chain(terminals_to_the_right.iter())
+ {
+ self.increase_pane_height_up(&terminal_id, count);
+ }
+ }
+ fn increase_pane_and_surroundings_down(&mut self, id: &PaneId, count: usize) {
+ let mut terminals_below = self
+ .pane_ids_directly_below(&id)
+ .expect("can't increase pane size down if there are no terminals below");
+ let terminal_borders_below: HashSet<usize> = terminals_below
+ .iter()
+ .map(|t| self.panes.get(t).unwrap().x())
+ .collect();
+ let (left_resize_border, terminals_to_the_left) =
+ self.bottom_aligned_contiguous_panes_to_the_left(&id, &terminal_borders_below);
+ let (right_resize_border, terminals_to_the_right) =
+ self.bottom_aligned_contiguous_panes_to_the_right(&id, &terminal_borders_below);
+ terminals_below.retain(|t| {
+ self.pane_is_between_vertical_borders(t, left_resize_border, right_resize_border)
+ });
+ self.increase_pane_height_down(&id, count);
+ for terminal_id in terminals_below {
+ self.reduce_pane_height_down(&terminal_id, count);
+ }
+ for terminal_id in terminals_to_the_left
+ .iter()
+ .chain(terminals_to_the_right.iter())
+ {
+ self.increase_pane_height_down(&terminal_id, count);
+ }
+ }
+ fn increase_pane_and_surroundings_right(&mut self, id: &PaneId, count: usize) {
+ let mut terminals_to_the_right = self
+ .pane_ids_directly_right_of(&id)
+ .expect("can't increase pane size right if there are no terminals to the right");
+ let terminal_borders_to_the_right: HashSet<usize> = terminals_to_the_right
+ .iter()
+ .map(|t| self.panes.get(t).unwrap().y())
+ .collect();
+ let (top_resize_border, terminals_above) =
+ self.right_aligned_contiguous_panes_above(&id, &terminal_borders_to_the_right);
+ let (bottom_resize_border, terminals_below) =
+ self.right_aligned_contiguous_panes_below(&id, &terminal_borders_to_the_right);
+ terminals_to_the_right.retain(|t| {
+ self.pane_is_between_horizontal_borders(t, top_resize_border, bottom_resize_border)
+ });
+ self.increase_pane_width_right(&id, count);
+ for terminal_id in terminals_to_the_right {
+ self.reduce_pane_width_right(&terminal_id, count);
+ }
+ for terminal_id in terminals_above.iter().chain(terminals_below.iter()) {
+ self.increase_pane_width_right(&terminal_id, count);
+ }
+ }
+ fn increase_pane_and_surroundings_left(&mut self, id: &PaneId, count: usize) {
+ let mut terminals_to_the_left = self
+ .pane_ids_directly_left_of(&id)
+ .expect("can't increase pane size right if there are no terminals to the right");
+ let terminal_borders_to_the_left: HashSet<usize> = terminals_to_the_left
+ .iter()
+ .map(|t| self.panes.get(t).unwrap().y())
+ .collect();
+ let (top_resize_border, terminals_above) =
+ self.left_aligned_contiguous_panes_above(&id, &terminal_borders_to_the_left);
+ let (bottom_resize_border, terminals_below) =
+ self.left_aligned_contiguous_panes_below(&id, &terminal_borders_to_the_left);
+ terminals_to_the_left.retain(|t| {
+ self.pane_is_between_horizontal_borders(t, top_resize_border, bottom_resize_border)
+ });
+ self.increase_pane_width_left(&id, count);
+ for terminal_id in terminals_to_the_left {
+ self.reduce_pane_width_left(&terminal_id, count);
+ }
+ for terminal_id in terminals_above.iter().chain(terminals_below.iter()) {
+ self.increase_pane_width_left(&terminal_id, count);
+ }
+ }
+ fn can_increase_pane_and_surroundings_right(
+ &self,
+ pane_id: &PaneId,
+ increase_by: usize,
+ ) -> bool {
+ let pane = self.panes.get(pane_id).unwrap();
+ let can_increase_pane_size = pane
+ .max_width()
+ .map(|max_width| pane.columns() + increase_by <= max_width)
+ .unwrap_or(true); // no max width, increase to your heart's content
+ if !can_increase_pane_size {
+ return false;
+ }
+ if let Some(panes_to_the_right) = self.pane_ids_directly_right_of(&pane_id) {
+ return panes_to_the_right.iter().all(|id| {
+ let p = self.panes.get(id).unwrap();
+ p.columns() > increase_by && p.columns() - increase_by >= p.min_width()
+ });
+ } else {
+ return false;
+ }
+ }
+ fn can_increase_pane_and_surroundings_left(
+ &self,
+ pane_id: &PaneId,
+ increase_by: usize,
+ ) -> bool {
+ let pane = self.panes.get(pane_id).unwrap();
+ let can_increase_pane_size = pane
+ .max_width()
+ .map(|max_width| pane.columns() + increase_by <= max_width)
+ .unwrap_or(true); // no max width, increase to your heart's content
+ if !can_increase_pane_size {
+ return false;
+ }
+ if let Some(panes_to_the_left) = self.pane_ids_directly_left_of(&pane_id) {
+ return panes_to_the_left.iter().all(|id| {
+ let p = self.panes.get(id).unwrap();
+ p.columns() > increase_by && p.columns() - increase_by >= p.min_width()
+ });
+ } else {
+ return false;
+ }
+ }
+ fn can_increase_pane_and_surroundings_down(
+ &self,
+ pane_id: &PaneId,
+ increase_by: usize,
+ ) -> bool {
+ let pane = self.panes.get(pane_id).unwrap();
+ let can_increase_pane_size = pane
+ .max_height()
+ .map(|max_height| pane.rows() + increase_by <= max_height)
+ .unwrap_or(true); // no max width, increase to your heart's content
+ if !can_increase_pane_size {
+ return false;
+ }
+ if let Some(panes_below) = self.pane_ids_directly_below(&pane_id) {
+ return panes_below.iter().all(|id| {
+ let p = self.panes.get(id).unwrap();
+ p.rows() > increase_by && p.rows() - increase_by >= p.min_height()
+ });
+ } else {
+ return false;
+ }
+ }
+ fn can_increase_pane_and_surroundings_up(&self, pane_id: &PaneId, increase_by: usize) -> bool {
+ let pane = self.panes.get(pane_id).unwrap();
+ let can_increase_pane_size = pane
+ .max_height()
+ .map(|max_height| pane.rows() + increase_by <= max_height)
+ .unwrap_or(true); // no max width, increase to your heart's content
+ if !can_increase_pane_size {
+ return false;
+ }
+ if let Some(panes_above) = self.pane_ids_directly_above(&pane_id) {
+ return panes_above.iter().all(|id| {
+ let p = self.panes.get(id).unwrap();
+ p.rows() > increase_by && p.rows() - increase_by >= p.min_height()
+ });
+ } else {
+ return false;
+ }
+ }
+ fn can_reduce_pane_and_surroundings_right(&self, pane_id: &PaneId, reduce_by: usize) -> bool {
+ let pane = self.panes.get(pane_id).unwrap();
+ let pane_columns = pane.columns();
+ let can_reduce_pane_size =
+ pane_columns > reduce_by && pane_columns - reduce_by >= pane.min_width();
+ if !can_reduce_pane_size {
+ return false;
+ }
+ if let Some(panes_to_the_left) = self.pane_ids_directly_left_of(&pane_id) {
+ return panes_to_the_left.iter().all(|id| {
+ let p = self.panes.get(id).unwrap();
+ p.max_width()
+ .map(|max_width| p.columns() + reduce_by <= max_width)
+ .unwrap_or(true) // no max width, increase to your heart's content
+ });
+ } else {
+ return false;
+ }
+ }
+ fn can_reduce_pane_and_surroundings_left(&self, pane_id: &PaneId, reduce_by: usize) -> bool {
+ let pane = self.panes.get(pane_id).unwrap();
+ let pane_columns = pane.columns();
+ let can_reduce_pane_size =
+ pane_columns > reduce_by && pane_columns - reduce_by >= pane.min_width();
+ if !can_reduce_pane_size {
+ return false;
+ }
+ if let Some(panes_to_the_right) = self.pane_ids_directly_right_of(&pane_id) {
+ return panes_to_the_right.iter().all(|id| {
+ let p = self.panes.get(id).unwrap();
+ p.max_width()
+ .map(|max_width| p.columns() + reduce_by <= max_width)
+ .unwrap_or(true) // no max width, increase to your heart's content
+ });
+ } else {
+ return false;
+ }
+ }
+ fn can_reduce_pane_and_surroundings_down(&self, pane_id: &PaneId, reduce_by: usize) -> bool {
+ let pane = self.panes.get(pane_id).unwrap();
+ let pane_rows = pane.rows();
+ let can_reduce_pane_size =
+ pane_rows > reduce_by && pane_rows - reduce_by >= pane.min_height();
+ if !can_reduce_pane_size {
+ return false;
+ }
+ if let Some(panes_above) = self.pane_ids_directly_above(&pane_id) {
+ return panes_above.iter().all(|id| {
+ let p = self.panes.get(id).unwrap();
+ p.max_height()
+ .map(|max_height| p.rows() + reduce_by <= max_height)
+ .unwrap_or(true) // no max height, increase to your heart's content
+ });
+ } else {
+ return false;
+ }
+ }
+ fn can_reduce_pane_and_surroundings_up(&self, pane_id: &PaneId, reduce_by: usize) -> bool {
+ let pane = self.panes.get(pane_id).unwrap();
+ let pane_rows = pane.rows();
+ let can_reduce_pane_size =
+ pane_rows > reduce_by && pane_rows - reduce_by >= pane.min_height();
+ if !can_reduce_pane_size {
+ return false;
+ }
+ if let Some(panes_below) = self.pane_ids_directly_below(&pane_id) {
+ return panes_below.iter().all(|id| {
+ let p = self.panes.get(id).unwrap();
+ p.max_height()
+ .map(|max_height| p.rows() + reduce_by <= max_height)
+ .unwrap_or(true) // no max height, increase to your heart's content
+ });
+ } else {
+ return false;
+ }
+ }
+ pub fn resize_right(&mut self) {
+ // TODO: find out by how much we actually reduced and only reduce by that much
+ let count = 10;
+ if let Some(active_pane_id) = self.get_active_pane_id() {
+ if self.can_increase_pane_and_surroundings_right(&active_pane_id, count) {
+ self.increase_pane_and_surroundings_right(&active_pane_id, count);
+ } else if self.can_reduce_pane_and_surroundings_right(&active_pane_id, count) {
+ self.reduce_pane_and_surroundings_right(&active_pane_id, count);
+ }
+ self.render();
+ }
+ }
+ pub fn resize_left(&mut self) {
+ // TODO: find out by how much we actually reduced and only reduce by that much
+ let count = 10;
+ if let Some(active_pane_id) = self.get_active_pane_id() {
+ if self.can_increase_pane_and_surroundings_left(&active_pane_id, count) {
+ self.increase_pane_and_surroundings_left(&active_pane_id, count);
+ } else if self.can_reduce_pane_and_surroundings_left(&active_pane_id, count) {
+ self.reduce_pane_and_surroundings_left(&active_pane_id, count);
+ }
+ self.render();
+ }
+ }
+ pub fn resize_down(&mut self) {
+ // TODO: find out by how much we actually reduced and only reduce by that much
+ let count = 2;
+ if let Some(active_pane_id) = self.get_active_pane_id() {
+ if self.can_increase_pane_and_surroundings_down(&active_pane_id, count) {
+ self.increase_pane_and_surroundings_down(&active_pane_id, count);
+ } else if self.can_reduce_pane_and_surroundings_down(&active_pane_id, count) {
+ self.reduce_pane_and_surroundings_down(&active_pane_id, count);
+ }
+ self.render();
+ }
+ }
+ pub fn resize_up(&mut self) {
+ // TODO: find out by how much we actually reduced and only reduce by that much
+ let count = 2;
+ if let Some(active_pane_id) = self.get_active_pane_id() {
+ if self.can_increase_pane_and_surroundings_up(&active_pane_id, count) {
+ self.increase_pane_and_surroundings_up(&active_pane_id, count);
+ } else if self.can_reduce_pane_and_surroundings_up(&active_pane_id, count) {
+ self.reduce_pane_and_surroundings_up(&active_pane_id, count);
+ }
+ self.render();
+ }
+ }
+ pub fn move_focus(&mut self) {
+ if !self.has_selectable_panes() {
+ return;
+ }
+ if self.fullscreen_is_active {
+ return;
+ }
+ let active_terminal_id = self.get_active_pane_id().unwrap();
+ let terminal_ids: Vec<PaneId> = self.get_selectable_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations
+ let first_terminal = terminal_ids.get(0).unwrap();
+ let active_terminal_id_position = terminal_ids
+ .iter()
+ .position(|id| id == &active_terminal_id)
+ .unwrap();
+ if let Some(next_terminal) = terminal_ids.get(active_terminal_id_position + 1) {
+ self.active_terminal = Some(*next_terminal);
+ } else {
+ self.active_terminal = Some(*first_terminal);
+ }
+ self.render();
+ }
+ pub fn move_focus_left(&mut self) {
+ if !self.has_selectable_panes() {
+ return;
+ }
+ if self.fullscreen_is_active {
+ return;
+ }
+ let active_terminal = self.get_active_pane();
+ if let Some(active) = active_terminal {
+ let terminals = self.get_selectable_panes();
+ let next_index = terminals
+ .enumerate()
+ .filter(|(_, (_, c))| {
+ c.is_directly_left_of(active) && c.horizontally_overlaps_with(active)
+ })
+ .max_by_key(|(_, (_, c))| c.get_horizontal_overlap_with(active))
+ .map(|(_, (pid, _))| pid);
+ match next_index {
+ Some(&p) => {
+ self.active_terminal = Some(p);
+ }
+ None => {
+ self.active_terminal = Some(active.pid());
+ }
+ }
+ } else {
+ self.active_terminal = Some(active_terminal.unwrap().pid());
+ }
+ self.render();
+ }
+ pub fn move_focus_down(&mut self) {
+ if !self.has_selectable_panes() {
+ return;
+ }
+ if self.fullscreen_is_active {
+ return;
+ }
+ let active_terminal = self.get_active_pane();
+ if let Some(active) = active_terminal {
+ let terminals = self.get_selectable_panes();
+ let next_index = terminals
+ .enumerate()
+ .filter(|(_, (_, c))| {
+ c.is_directly_below(active) && c.vertically_overlaps_with(active)
+ })
+ .max_by_key(|(_, (_, c))| c.get_vertical_overlap_with(active))
+ .map(|(_, (pid, _))| pid);
+ match next_index {
+ Some(&p) => {
+ self.active_terminal = Some(p);
+ }
+ None => {
+ self.active_terminal = Some(active.pid());
+ }
+ }
+ } else {
+ self.active_terminal = Some(active_terminal.unwrap().pid());
+ }
+ self.render();
+ }
+ pub fn move_focus_up(&mut self) {
+ if !self.has_selectable_panes() {
+ return;
+ }
+ if self.fullscreen_is_active {
+ return;
+ }
+ let active_terminal = self.get_active_pane();
+ if let Some(active) = active_terminal {
+ let terminals = self.get_selectable_panes();
+ let next_index = terminals
+ .enumerate()
+ .filter(|(_, (_, c))| {
+ c.is_directly_above(active) && c.vertically_overlaps_with(active)
+ })
+ .max_by_key(|(_, (_, c))| c.get_vertical_overlap_with(active))
+ .map(|(_, (pid, _))| pid);
+ match next_index {
+ Some(&p) => {
+ self.active_terminal = Some(p);
+ }
+ None => {
+ self.active_terminal = Some(active.pid());
+ }
+ }
+ } else {
+ self.active_terminal = Some(active_terminal.unwrap().pid());
+ }
+ self.render();
+ }
+ pub fn move_focus_right(&mut self) {
+ if !self.has_selectable_panes() {
+ return;
+ }
+ if self.fullscreen_is_active {
+ return;
+ }
+ let active_terminal = self.get_active_pane();
+ if let Some(active) = active_terminal {
+ let terminals = self.get_selectable_panes();
+ let next_index = terminals
+ .enumerate()
+ .filter(|(_, (_, c))| {
+ c.is_directly_right_of(active) && c.horizontally_overlaps_with(active)
+ })
+ .max_by_key(|(_, (_, c))| c.get_horizontal_overlap_with(active))
+ .map(|(_, (pid, _))| pid);
+ match next_index {
+ Some(&p) => {
+ self.active_terminal = Some(p);
+ }
+ None => {
+ self.active_terminal = Some(active.pid());
+ }
+ }
+ } else {
+ self.active_terminal = Some(active_terminal.unwrap().pid());
+ }
+ self.render();
+ }
+ fn horizontal_borders(&self, terminals: &[PaneId]) -> HashSet<usize> {
+ terminals.iter().fold(HashSet::new(), |mut borders, t| {
+ let terminal = self.panes.get(t).unwrap();
+ borders.insert(terminal.y());
+ borders.insert(terminal.y() + terminal.rows() + 1); // 1 for the border width
+ borders
+ })
+ }
+ fn vertical_borders(&self, terminals: &[PaneId]) -> HashSet<usize> {
+ terminals.iter().fold(HashSet::new(), |mut borders, t| {
+ let terminal = self.panes.get(t).unwrap();
+ borders.insert(terminal.x());
+ borders.insert(terminal.x() + terminal.columns() + 1); // 1 for the border width
+ borders
+ })
+ }
+ fn panes_to_the_left_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> {
+ if let Some(terminal) = self.panes.get(&id) {
+ let upper_close_border = terminal.y();
+ let lower_close_border = terminal.y() + terminal.rows() + 1;
+
+ if let Some(mut terminals_to_the_left) = self.pane_ids_directly_left_of(&id) {
+ let terminal_borders_to_the_left = self.horizontal_borders(&terminals_to_the_left);
+ if terminal_borders_to_the_left.contains(&upper_close_border)
+ && terminal_borders_to_the_left.contains(&lower_close_border)
+ {
+ terminals_to_the_left.retain(|t| {
+ self.pane_is_between_horizontal_borders(
+ t,
+ upper_close_border,
+ lower_close_border,
+ )
+ });
+ return Some(terminals_to_the_left);
+ }
+ }
+ }
+ None
+ }
+ fn panes_to_the_right_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> {
+ if let Some(terminal) = self.panes.get(&id) {
+ let upper_close_border = terminal.y();
+ let lower_close_border = terminal.y() + terminal.rows() + 1;
+
+ if let Some(mut terminals_to_the_right) = self.pane_ids_directly_right_of(&id) {
+ let terminal_borders_to_the_right =
+ self.horizontal_borders(&terminals_to_the_right);
+ if terminal_borders_to_the_right.contains(&upper_close_border)
+ && terminal_borders_to_the_right.contains(&lower_close_border)
+ {
+ terminals_to_the_right.retain(|t| {
+ self.pane_is_between_horizontal_borders(
+ t,
+ upper_close_border,
+ lower_close_border,
+ )
+ });
+ return Some(terminals_to_the_right);
+ }
+ }
+ }
+ None
+ }
+ fn panes_above_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> {
+ if let Some(terminal) = self.panes.get(&id) {
+ let left_close_border = terminal.x();
+ let right_close_border = terminal.x() + terminal.columns() + 1;
+
+ if let Some(mut terminals_above) = self.pane_ids_directly_above(&id) {
+ let terminal_borders_above = self.vertical_borders(&terminals_above);
+ if terminal_borders_above.contains(&left_close_border)
+ && terminal_borders_above.contains(&right_close_border)
+ {
+ terminals_above.retain(|t| {
+ self.pane_is_between_vertical_borders(
+ t,
+ left_close_border,
+ right_close_border,
+ )
+ });
+ return Some(terminals_above);
+ }
+ }
+ }
+ None
+ }
+ fn panes_below_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> {
+ if let Some(terminal) = self.panes.get(&id) {
+ let left_close_border = terminal.x();
+ let right_close_border = terminal.x() + terminal.columns() + 1;
+
+ if let Some(mut terminals_below) = self.pane_ids_directly_below(&id) {
+ let terminal_borders_below = self.vertical_borders(&terminals_below);
+ if terminal_borders_below.contains(&left_close_border)
+ && terminal_borders_below.contains(&right_close_border)
+ {
+ terminals_below.retain(|t| {
+ self.pane_is_between_vertical_borders(
+ t,
+ left_close_border,
+ right_close_border,
+ )
+ });
+ return Some(terminals_below);
+ }
+ }
+ }
+ None
+ }
+ fn close_down_to_max_terminals(&mut self) {
+ if let Some(max_panes) = self.max_panes {
+ let terminals = self.get_pane_ids();
+ for &pid in terminals.iter().skip(max_panes - 1) {
+ self.send_pty_instructions
+ .send(PtyInstruction::ClosePane(pid))
+ .unwrap();
+ self.close_pane_without_rerender(pid);
+ }
+ }
+ }
+ pub fn get_pane_ids(&self) -> Vec<PaneId> {
+ self.get_panes().map(|(&pid, _)| pid).collect()
+ }
+ pub fn set_pane_selectable(&mut self, id: PaneId, selectable: bool) {
+ if let Some(pane) = self.panes.get_mut(&id) {
+ pane.set_selectable(selectable);
+ if self.get_active_pane_id() == Some(id) && !selectable {
+ self.active_terminal = self.next_active_pane(self.get_pane_ids())
+ }
+ }
+ }
+ pub fn set_pane_invisible_borders(&mut self, id: PaneId, invisible_borders: bool) {
+ debug_log_to_file(format!(
+ "set_pane_invisible_borders: {:?}",
+ invisible_borders
+ ));
+ if let Some(pane) = self.panes.get_mut(&id) {
+ pane.set_invisible_borders(invisible_borders);
+ }
+ }
+ pub fn set_pane_max_height(&mut self, id: PaneId, max_height: usize) {
+ if let Some(pane) = self.panes.get_mut(&id) {
+ pane.set_max_height(max_height);
+ }
+ }
+ pub fn close_pane(&mut self, id: PaneId) {
+ if self.panes.get(&id).is_some() {
+ self.close_pane_without_rerender(id);
+ }
+ }
+ pub fn close_pane_without_rerender(&mut self, id: PaneId) {
+ if let Some(terminal_to_close) = self.panes.get(&id) {
+ let terminal_to_close_width = terminal_to_close.columns();
+ let terminal_to_close_height = terminal_to_close.rows();
+ if let Some(terminals) = self.panes_to_the_left_between_aligning_borders(id) {
+ for terminal_id in terminals.iter() {
+ self.increase_pane_width_right(&terminal_id, terminal_to_close_width + 1);
+ // 1 for the border
+ }
+ if self.active_terminal == Some(id) {
+ self.active_terminal = self.next_active_pane(terminals);
+ }
+ } else if let Some(terminals) = self.panes_to_the_right_between_aligning_borders(id) {
+ for terminal_id in terminals.iter() {
+ self.increase_pane_width_left(&terminal_id, terminal_to_close_width + 1);
+ // 1 for the border
+ }
+ if self.active_terminal == Some(id) {
+ self.active_terminal = self.next_active_pane(terminals);
+ }
+ } else if let Some(terminals) = self.panes_above_between_aligning_borders(id) {
+ for terminal_id in terminals.iter() {
+ self.increase_pane_height_down(&terminal_id, terminal_to_close_height + 1);
+ // 1 for the border
+ }
+ if self.active_terminal == Some(id) {
+ self.active_terminal = self.next_active_pane(terminals);
+ }
+ } else if let Some(terminals) = self.panes_below_between_aligning_borders(id) {
+ for terminal_id in terminals.iter() {
+ self.increase_pane_height_up(&terminal_id, terminal_to_close_height + 1);
+ // 1 for the border
+ }
+ if self.active_terminal == Some(id) {
+ self.active_terminal = self.next_active_pane(terminals);
+ }
+ } else {
+ }
+ self.panes.remove(&id);
+ if self.active_terminal.is_none() {
+ self.active_terminal = self.next_active_pane(self.get_pane_ids());
+ }
+ }
+ }
+ pub fn close_focused_pane(&mut self) {
+ if let Some(active_pane_id) = self.get_active_pane_id() {
+ self.close_pane(active_pane_id);
+ self.send_pty_instructions
+ .send(PtyInstruction::ClosePane(active_pane_id))
+ .unwrap();
+ }
+ }
+ pub fn scroll_active_terminal_up(&mut self) {
+ if let Some(active_terminal_id) = self.get_active_terminal_id() {
+ let active_terminal = self
+ .panes
+ .get_mut(&PaneId::Terminal(active_terminal_id))
+ .unwrap();
+ active_terminal.scroll_up(1);
+ self.render();
+ }
+ }
+ pub fn scroll_active_terminal_down(&mut self) {
+ if let Some(active_terminal_id) = self.get_active_terminal_id() {
+ let active_terminal = self
+ .panes
+ .get_mut(&PaneId::Terminal(active_terminal_id))
+ .unwrap();
+ active_terminal.scroll_down(1);
+ self.render();
+ }
+ }
+ pub fn clear_active_terminal_scroll(&mut self) {
+ if let Some(active_terminal_id) = self.get_active_terminal_id() {
+ let active_terminal = self
+ .panes
+ .get_mut(&PaneId::Terminal(active_terminal_id))
+ .unwrap();
+ active_terminal.clear_scroll();
+ }
+ }
+}
diff --git a/src/common/command_is_executing.rs b/src/common/command_is_executing.rs
new file mode 100644
index 00000000..93c44eb6
--- /dev/null
+++ b/src/common/command_is_executing.rs
@@ -0,0 +1,53 @@
+#![allow(clippy::mutex_atomic)]
+use std::sync::{Arc, Condvar, Mutex};
+
+#[derive(Clone)]
+pub struct CommandIsExecuting {
+ opening_new_pane: Arc<(Mutex<bool>, Condvar)>,
+ closing_pane: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl CommandIsExecuting {
+ pub fn new() -> Self {
+ CommandIsExecuting {
+ opening_new_pane: Arc::new((Mutex::new(false), Condvar::new())),
+ closing_pane: Arc::new((Mutex::new(false), Condvar::new())),
+ }
+ }
+ pub fn closing_pane(&mut self) {
+ let (lock, _cvar) = &*self.closing_pane;
+ let mut closing_pane = lock.lock().unwrap();
+ *closing_pane = true;
+ }
+ pub fn done_closing_pane(&mut self) {
+ let (lock, cvar) = &*self.closing_pane;
+ let mut closing_pane = lock.lock().unwrap();
+ *closing_pane = false;
+ cvar.notify_one();
+ }
+ pub fn opening_new_pane(&mut self) {
+ let (lock, _cvar) = &*self.opening_new_pane;
+ let mut opening_new_pane = lock.lock().unwrap();
+ *opening_new_pane = true;
+ }
+ pub fn done_opening_new_pane(&mut self) {
+ let (lock, cvar) = &*self.opening_new_pane;
+ let mut opening_new_pane = lock.lock().unwrap();
+ *opening_new_pane = false;
+ cvar.notify_one();
+ }
+ pub fn wait_until_pane_is_closed(&self) {
+ let (lock, cvar) = &*self.closing_pane;
+ let mut closing_pane = lock.lock().unwrap();
+ while *closing_pane {
+ closing_pane = cvar.wait(closing_pane).unwrap();
+ }
+ }
+ pub fn wait_until_new_pane_is_opened(&self) {
+ let (lock, cvar) = &*self.opening_new_pane;
+ let mut opening_new_pane = lock.lock().unwrap();
+ while *opening_new_pane {
+ opening_new_pane = cvar.wait(opening_new_pane).unwrap();
+ }
+ }
+}
diff --git a/src/common/errors.rs b/src/common/errors.rs
new file mode 100644
index 00000000..27fe3103
--- /dev/null
+++ b/src/common/errors.rs
@@ -0,0 +1,288 @@
+use super::{AppInstruction, OPENCALLS};
+use crate::pty_bus::PtyInstruction;
+use crate::screen::ScreenInstruction;
+
+use std::fmt::{Display, Error, Formatter};
+
+const MAX_THREAD_CALL_STACK: usize = 6;
+
+#[cfg(not(test))]
+use super::SenderWithContext;
+#[cfg(not(test))]
+use std::panic::PanicInfo;
+#[cfg(not(test))]
+pub fn handle_panic(
+ info: &PanicInfo<'_>,
+ send_app_instructions: &SenderWithContext<AppInstruction>,
+) {
+ use backtrace::Backtrace;
+ use std::{process, thread};
+ let backtrace = Backtrace::new();
+ let thread = thread::current();
+ let thread = thread.name().unwrap_or("unnamed");
+
+ let msg = match info.payload().downcast_ref::<&'static str>() {
+ Some(s) => Some(*s),
+ None => info.payload().downcast_ref::<String>().map(|s| &**s),
+ };
+
+ let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
+
+ let backtrace = match (info.location(), msg) {
+ (Some(location), Some(msg)) => format!(
+ "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked at '{}': {}:{}\n\u{1b}[0;0m{:?}",
+ err_ctx,
+ thread,
+ msg,
+ location.file(),
+ location.line(),
+ backtrace
+ ),
+ (Some(location), None) => format!(
+ "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked: {}:{}\n\u{1b}[0;0m{:?}",
+ err_ctx,
+ thread,
+ location.file(),
+ location.line(),
+ backtrace
+ ),
+ (None, Some(msg)) => format!(
+ "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked at '{}'\n\u{1b}[0;0m{:?}",
+ err_ctx, thread, msg, backtrace
+ ),
+ (None, None) => format!(
+ "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked\n\u{1b}[0;0m{:?}",
+ err_ctx, thread, backtrace
+ ),
+ };
+
+ if thread == "main" {
+ println!("{}", backtrace);
+ process::exit(1);
+ } else {
+ send_app_instructions
+ .send(AppInstruction::Error(backtrace))
+ .unwrap();
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct ErrorContext {
+ calls: [ContextType; MAX_THREAD_CALL_STACK],
+}
+
+impl ErrorContext {
+ pub fn new() -> Self {
+ Self {
+ calls: [ContextType::Empty; MAX_THREAD_CALL_STACK],
+ }
+ }
+
+ pub fn add_call(&mut self, call: ContextType) {
+ for ctx in self.calls.iter_mut() {
+ if *ctx == ContextType::Empty {
+ *ctx = call;
+ break;
+ }
+ }
+ OPENCALLS.with(|ctx| *ctx.borrow_mut() = *self);
+ }
+}
+
+impl Default for ErrorContext {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Display for ErrorContext {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+ writeln!(f, "Originating Thread(s):")?;
+ for (index, ctx) in self.calls.iter().enumerate() {
+ if *ctx == ContextType::Empty {
+ break;
+ }
+ writeln!(f, "\u{1b}[0;0m{}. {}", index + 1, ctx)?;
+ }
+ Ok(())
+ }
+}
+
+#[derive(Copy, Clone, PartialEq)]
+pub enum ContextType {
+ Screen(ScreenContext),
+ Pty(PtyContext),
+
+ Plugin(PluginContext),
+ App(AppContext),
+ IPCServer,
+ StdinHandler,
+ AsyncTask,
+ Empty,
+}
+
+impl Display for ContextType {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+ let purple = "\u{1b}[1;35m";
+ let green = "\u{1b}[0;32m";
+ match *self {
+ ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c),
+ ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c),
+
+ ContextType::Plugin(c) => write!(f, "{}plugin_thread: {}{:?}", purple, green, c),
+ ContextType::App(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c),
+ ContextType::IPCServer => write!(f, "{}ipc_server: {}AcceptInput", purple, green),
+ ContextType::StdinHandler => {
+ write!(f, "{}stdin_handler_thread: {}AcceptInput", purple, green)
+ }
+ ContextType::AsyncTask => {
+ write!(f, "{}stream_terminal_bytes: {}AsyncTask", purple, green)
+ }
+ ContextType::Empty => write!(f, ""),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum ScreenContext {
+ HandlePtyEvent,
+ Render,
+ NewPane,
+ HorizontalSplit,
+ VerticalSplit,
+ WriteCharacter,
+ ResizeLeft,
+ ResizeRight,
+ ResizeDown,
+ ResizeUp,
+ MoveFocus,
+ MoveFocusLeft,
+ MoveFocusDown,
+ MoveFocusUp,
+ MoveFocusRight,
+ Quit,
+ ScrollUp,
+ ScrollDown,
+ ClearScroll,
+ CloseFocusedPane,
+ ToggleActiveTerminalFullscreen,
+ SetSelectable,
+ SetInvisibleBorders,
+ SetMaxHeight,
+ ClosePane,
+ ApplyLayout,
+ NewTab,
+ SwitchTabNext,
+ SwitchTabPrev,
+ CloseTab,
+}
+
+impl From<&ScreenInstruction> for ScreenContext {
+ fn from(screen_instruction: &ScreenInstruction) -> Self {
+ match *screen_instruction {
+ ScreenInstruction::Pty(..) => ScreenContext::HandlePtyEvent,
+ ScreenInstruction::Render => ScreenContext::Render,
+ ScreenInstruction::NewPane(_) => ScreenContext::NewPane,
+ ScreenInstruction::HorizontalSplit(_) => ScreenContext::HorizontalSplit,
+ ScreenInstruction::VerticalSplit(_) => ScreenContext::VerticalSplit,
+ ScreenInstruction::WriteCharacter(_) => ScreenContext::WriteCharacter,
+ ScreenInstruction::ResizeLeft => ScreenContext::ResizeLeft,
+ ScreenInstruction::ResizeRight => ScreenContext::ResizeRight,
+ ScreenInstruction::ResizeDown => ScreenContext::ResizeDown,
+ ScreenInstruction::ResizeUp => ScreenContext::ResizeUp,
+ ScreenInstruction::MoveFocus => ScreenContext::MoveFocus,
+ ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft,
+ ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown,
+ ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp,
+ ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight,
+ ScreenInstruction::Quit => ScreenContext::Quit,
+ ScreenInstruction::ScrollUp => ScreenContext::ScrollUp,
+ ScreenInstruction::ScrollDown => ScreenContext::ScrollDown,
+ ScreenInstruction::ClearScroll => ScreenContext::ClearScroll,
+ ScreenInstruction::CloseFocusedPane => ScreenContext::CloseFocusedPane,
+ ScreenInstruction::ToggleActiveTerminalFullscreen => {
+ ScreenContext::ToggleActiveTerminalFullscreen
+ }
+ ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable,
+ ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders,
+ ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight,
+ ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane,
+ ScreenInstruction::ApplyLayout(_) => ScreenContext::ApplyLayout,
+ ScreenInstruction::NewTab(_) => ScreenContext::NewTab,
+ ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext,
+ ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev,
+ ScreenInstruction::CloseTab => ScreenContext::CloseTab,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum PtyContext {
+ SpawnTerminal,
+ SpawnTerminalVertically,
+ SpawnTerminalHorizontally,
+ NewTab,
+ ClosePane,
+ CloseTab,
+ Quit,
+}
+
+impl From<&PtyInstruction> for PtyContext {
+ fn from(pty_instruction: &PtyInstruction) -> Self {
+ match *pty_instruction {
+ PtyInstruction::SpawnTerminal(_) => PtyContext::SpawnTerminal,
+ PtyInstruction::SpawnTerminalVertically(_) => PtyContext::SpawnTerminalVertically,
+ PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally,
+ PtyInstruction::ClosePane(_) => PtyContext::ClosePane,
+ PtyInstruction::CloseTab(_) => PtyContext::CloseTab,
+ PtyInstruction::NewTab => PtyContext::NewTab,
+ PtyInstruction::Quit => PtyContext::Quit,
+ }
+ }
+}
+
+// FIXME: This whole pattern *needs* a macro eventually, it's soul-crushing to write
+
+use crate::wasm_vm::PluginInstruction;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum PluginContext {
+ Load,
+ Draw,
+ Input,
+ GlobalInput,
+ Unload,
+ Quit,
+}
+
+impl From<&PluginInstruction> for PluginContext {
+ fn from(plugin_instruction: &PluginInstruction) -> Self {
+ match *plugin_instruction {
+ PluginInstruction::Load(..) => PluginContext::Load,
+ PluginInstruction::Draw(..) => PluginContext::Draw,
+ PluginInstruction::Input(..) => PluginContext::Input,
+ PluginInstruction::GlobalInput(_) => PluginContext::GlobalInput,
+ PluginInstruction::Unload(_) => PluginContext::Unload,
+ PluginInstruction::Quit => PluginContext::Quit,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum AppContext {
+ GetState,
+ SetState,
+ Exit,
+ Error,
+}
+
+impl From<&AppInstruction> for AppContext {
+ fn from(app_instruction: &AppInstruction) -> Self {
+ match *app_instruction {
+ AppInstruction::GetState(_) => AppContext::GetState,
+ AppInstruction::SetState(_) => AppContext::SetState,
+ AppInstruction::Exit => AppContext::Exit,
+ AppInstruction::Error(_) => AppContext::Error,
+ }
+ }
+}
diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs
new file mode 100644
index 00000000..6f4a4521
--- /dev/null
+++ b/src/common/input/actions.rs
@@ -0,0 +1,47 @@
+/// This module is for defining the set of actions that can be taken in response to a keybind
+/// and also passing actions back to the handler for dispatch.
+use super::handler;
+
+#[derive(Clone)]
+pub enum Direction {
+ Left,
+ Right,
+ Up,
+ Down,
+}
+
+#[derive(Clone)]
+pub enum Action {
+ /// Quit mosaic
+ Quit,
+ /// Write to terminal
+ Write(Vec<u8>),
+ /// Switch to the specified input mode
+ SwitchToMode(handler::InputMode),
+ TogglePersistentMode,
+ /// Resize focus pane in specified direction
+ Resize(Direction),
+ /// Switch focus to next pane in specified direction
+ SwitchFocus(Direction),
+ /// Move the focus pane in specified direction
+ MoveFocus(Direction),
+ /// Scroll up in focus pane
+ ScrollUp,
+ /// Scroll down in focus pane
+ ScrollDown,
+ /// Toggle focus pane between fullscreen and normal layout
+ ToggleFocusFullscreen,
+ /// Open a new pane in specified direction (relative to focus)
+ /// If no direction is specified, will try to use the biggest available space
+ NewPane(Option<Direction>),
+ /// Close focus pane
+ CloseFocus,
+ // Create a new tab
+ NewTab,
+ // Go to next tab
+ GoToNextTab,
+ // Go to previous tab
+ GoToPreviousTab,
+ // Close the current tab
+ CloseTab,
+}
diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs
new file mode 100644
index 00000000..2a1c2c72
--- /dev/null
+++ b/src/common/input/handler.rs
@@ -0,0 +1,349 @@
+use super::actions::Action;
+use super::keybinds::get_default_keybinds;
+use crate::common::{update_state, AppInstruction, AppState, SenderWithContext, OPENCALLS};
+/// Module for handling input
+use crate::errors::ContextType;
+use crate::os_input_output::OsApi;
+use crate::pty_bus::PtyInstruction;
+use crate::screen::ScreenInstruction;
+use crate::wasm_vm::PluginInstruction;
+use crate::CommandIsExecuting;
+
+use serde::{Deserialize, Serialize};
+use strum_macros::EnumIter;
+use termion::input::TermReadEventsAndRaw;
+
+use super::keybinds::key_to_action;
+
+struct InputHandler {
+ mode: InputMode,
+ mode_is_persistent: bool,
+ os_input: Box<dyn OsApi>,
+ command_is_executing: CommandIsExecuting,
+ send_screen_instructions: SenderWithContext<ScreenInstruction>,
+ send_pty_instructions: SenderWithContext<PtyInstruction>,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ send_app_instructions: SenderWithContext<AppInstruction>,
+}
+
+impl InputHandler {
+ fn new(
+ os_input: Box<dyn OsApi>,
+ command_is_executing: CommandIsExecuting,
+ send_screen_instructions: SenderWithContext<ScreenInstruction>,
+ send_pty_instructions: SenderWithContext<PtyInstruction>,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ send_app_instructions: SenderWithContext<AppInstruction>,
+ ) -> Self {
+ InputHandler {
+ mode: InputMode::Normal,
+ mode_is_persistent: false,
+ os_input,
+ command_is_executing,
+ send_screen_instructions,
+ send_pty_instructions,
+ send_plugin_instructions,
+ send_app_instructions,
+ }
+ }
+
+ /// Main event loop
+ fn get_input(&mut self) {
+ let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
+ err_ctx.add_call(ContextType::StdinHandler);
+ self.send_pty_instructions.update(err_ctx);
+ self.send_app_instructions.update(err_ctx);
+ self.send_screen_instructions.update(err_ctx);
+ if let Ok(keybinds) = get_default_keybinds() {
+ 'input_loop: loop {
+ let entry_mode = self.mode;
+ //@@@ I think this should actually just iterate over stdin directly
+ let stdin_buffer = self.os_input.read_from_stdin();
+ drop(
+ self.send_plugin_instructions
+ .send(PluginInstruction::GlobalInput(stdin_buffer.clone())),
+ );
+ for key_result in stdin_buffer.events_and_raw() {
+ match key_result {
+ Ok((event, raw_bytes)) => match event {
+ termion::event::Event::Key(key) => {
+ let should_break = self.dispatch_action(key_to_action(
+ &key, raw_bytes, &self.mode, &keybinds,
+ ));
+ //@@@ This is a hack until we dispatch more than one action per key stroke
+ if entry_mode == self.mode && !self.mode_is_persistent {
+ self.mode = InputMode::Normal;
+ update_state(&self.send_app_instructions, |_| AppState {
+ input_state: InputState {
+ mode: self.mode,
+ persistent: self.mode_is_persistent,
+ },
+ });
+ }
+ if should_break {
+ break 'input_loop;
+ }
+ }
+ termion::event::Event::Mouse(_)
+ | termion::event::Event::Unsupported(_) => {
+ unimplemented!("Mouse and unsupported events aren't supported!");
+ }
+ },
+ Err(err) => panic!("Encountered read error: {:?}", err),
+ }
+ }
+ }
+ } else {
+ //@@@ Error handling?
+ self.exit();
+ }
+ }
+
+ fn dispatch_action(&mut self, action: Action) -> bool {
+ let mut interrupt_loop = false;
+
+ match action {
+ Action::Write(val) => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::ClearScroll)
+ .unwrap();
+ self.send_screen_instructions
+ .send(ScreenInstruction::WriteCharacter(val))
+ .unwrap();
+ }
+ Action::Quit => {
+ self.exit();
+ interrupt_loop = true;
+ }
+ Action::SwitchToMode(mode) => {
+ self.mode = mode;
+ if mode == InputMode::Normal {
+ self.mode_is_persistent = false;
+ }
+ update_state(&self.send_app_instructions, |_| AppState {
+ input_state: InputState {
+ mode: self.mode,
+ persistent: self.mode_is_persistent,
+ },
+ });
+ self.send_screen_instructions
+ .send(ScreenInstruction::Render)
+ .unwrap();
+ }
+ Action::TogglePersistentMode => {
+ self.mode_is_persistent = !self.mode_is_persistent;
+ update_state(&self.send_app_instructions, |_| AppState {
+ input_state: InputState {
+ mode: self.mode,
+ persistent: self.mode_is_persistent,
+ },
+ });
+ }
+ Action::Resize(direction) => {
+ let screen_instr = match direction {
+ super::actions::Direction::Left => ScreenInstruction::ResizeLeft,
+ super::actions::Direction::Right => ScreenInstruction::ResizeRight,
+ super::actions::Direction::Up => ScreenInstruction::ResizeUp,
+ super::actions::Direction::Down => ScreenInstruction::ResizeDown,
+ };
+ self.send_screen_instructions.send(screen_instr).unwrap();
+ }
+ Action::SwitchFocus(_) => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::MoveFocus)
+ .unwrap();
+ }
+ Action::MoveFocus(direction) => {
+ let screen_instr = match direction {
+ super::actions::Direction::Left => ScreenInstruction::MoveFocusLeft,
+ super::actions::Direction::Right => ScreenInstruction::MoveFocusRight,
+ super::actions::Direction::Up => ScreenInstruction::MoveFocusUp,
+ super::actions::Direction::Down => ScreenInstruction::MoveFocusDown,
+ };
+ self.send_screen_instructions.send(screen_instr).unwrap();
+ }
+ Action::ScrollUp => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::ScrollUp)
+ .unwrap();
+ }
+ Action::ScrollDown => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::ScrollDown)
+ .unwrap();
+ }
+ Action::ToggleFocusFullscreen => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::ToggleActiveTerminalFullscreen)
+ .unwrap();
+ }
+ Action::NewPane(direction) => {
+ let pty_instr = match direction {
+ Some(super::actions::Direction::Left) => {
+ PtyInstruction::SpawnTerminalVertically(None)
+ }
+ Some(super::actions::Direction::Right) => {
+ PtyInstruction::SpawnTerminalVertically(None)
+ }
+ Some(super::actions::Direction::Up) => {
+ PtyInstruction::SpawnTerminalHorizontally(None)
+ }
+ Some(super::actions::Direction::Down) => {
+ PtyInstruction::SpawnTerminalHorizontally(None)
+ }
+ // No direction specified - try to put it in the biggest available spot
+ None => PtyInstruction::SpawnTerminal(None),
+ };
+ self.command_is_executing.opening_new_pane();
+ self.send_pty_instructions.send(pty_instr).unwrap();
+ self.command_is_executing.wait_until_new_pane_is_opened();
+ }
+ Action::CloseFocus => {
+ self.command_is_executing.closing_pane();
+ self.send_screen_instructions
+ .send(ScreenInstruction::CloseFocusedPane)
+ .unwrap();
+ self.command_is_executing.wait_until_pane_is_closed();
+ }
+ Action::NewTab => {
+ self.command_is_executing.opening_new_pane();
+ self.send_pty_instructions
+ .send(PtyInstruction::NewTab)
+ .unwrap();
+ self.command_is_executing.wait_until_new_pane_is_opened();
+ }
+ Action::GoToNextTab => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::SwitchTabNext)
+ .unwrap();
+ }
+ Action::GoToPreviousTab => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::SwitchTabPrev)
+ .unwrap();
+ }
+ Action::CloseTab => {
+ self.command_is_executing.closing_pane();
+ self.send_screen_instructions
+ .send(ScreenInstruction::CloseTab)
+ .unwrap();
+ self.command_is_executing.wait_until_pane_is_closed();
+ }
+ }
+
+ interrupt_loop
+ }
+
+ /// Routine to be called when the input handler exits (at the moment this is the
+ /// same as quitting mosaic)
+ fn exit(&mut self) {
+ self.send_app_instructions
+ .send(AppInstruction::Exit)
+ .unwrap();
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
+pub struct InputState {
+ mode: InputMode,
+ persistent: bool,
+}
+
+impl Default for InputState {
+ fn default() -> InputState {
+ InputState {
+ mode: InputMode::Normal,
+ persistent: false,
+ }
+ }
+}
+
+/// Dictates whether we're in command mode, persistent command mode, normal mode or exiting:
+/// - Normal mode either writes characters to the terminal, or switches to command mode
+/// using a particular key control
+/// - Command mode intercepts characters to control mosaic itself, before switching immediately
+/// back to normal mode
+/// - Persistent command mode is the same as command mode, but doesn't return automatically to
+/// normal mode
+#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, EnumIter, Serialize, Deserialize)]
+pub enum InputMode {
+ Normal,
+ Command,
+ Resize,
+ Pane,
+ Tab,
+ Scroll,
+ Exiting,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize)]
+pub struct Help {
+ pub mode: InputMode,
+ pub mode_is_persistent: bool,
+ pub keybinds: Vec<(String, String)>, // <shortcut> => <shortcut description>
+}
+
+impl Default for InputMode {
+ fn default() -> InputMode {
+ InputMode::Normal
+ }
+}
+
+pub fn get_help(input_state: &InputState) -> Help {
+ let mut keybinds: Vec<(String, String)> = vec![];
+ match input_state.mode {
+ InputMode::Normal | InputMode::Command | InputMode::Exiting => {
+ keybinds.push((format!("p"), format!("Pane mode")));
+ keybinds.push((format!("t"), format!("Tab mode")));
+ keybinds.push((format!("r"), format!("Resize mode")));
+ }
+ InputMode::Resize => {
+ keybinds.push((format!("←↓↑→"), format!("resize pane")));
+ }
+ InputMode::Pane => {
+ keybinds.push((format!("←↓↑→"), format!("move focus")));
+ keybinds.push((format!("p"), format!("next pane")));
+ keybinds.push((format!("n"), format!("new pane")));
+ keybinds.push((format!("d"), format!("down split")));
+ keybinds.push((format!("r"), format!("right split")));
+ keybinds.push((format!("x"), format!("exit pane")));
+ keybinds.push((format!("f"), format!("fullscreen pane")));
+ }
+ InputMode::Tab => {
+ keybinds.push((format!("←↓↑→"), format!("move tab focus")));
+ keybinds.push((format!("n"), format!("new tab")));
+ keybinds.push((format!("x"), format!("exit tab")));
+ }
+ InputMode::Scroll => {
+ keybinds.push((format!("↓↑"), format!("scroll up/down")));
+ }
+ }
+ keybinds.push((format!("ESC"), format!("Back")));
+ keybinds.push((format!("q"), format!("Quit")));
+ Help {
+ mode: input_state.mode,
+ mode_is_persistent: input_state.persistent,
+ keybinds,
+ }
+}
+
+/// Entry point to the module that instantiates a new InputHandler and calls its
+/// reading loop
+pub fn input_loop(
+ os_input: Box<dyn OsApi>,
+ command_is_executing: CommandIsExecuting,
+ send_screen_instructions: SenderWithContext<ScreenInstruction>,
+ send_pty_instructions: SenderWithContext<PtyInstruction>,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ send_app_instructions: SenderWithContext<AppInstruction>,
+) {
+ let _handler = InputHandler::new(
+ os_input,
+ command_is_executing,
+ send_screen_instructions,
+ send_pty_instructions,
+ send_plugin_instructions,
+ send_app_instructions,
+ )
+ .get_input();
+}
diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs
new file mode 100644
index 00000000..d6ace68d
--- /dev/null
+++ b/src/common/input/keybinds.rs
@@ -0,0 +1,143 @@
+// This module is for mapping particular input keys to their corresponding actions.
+
+use super::actions::{Action, Direction};
+use super::handler::InputMode;
+
+use std::collections::HashMap;
+
+use strum::IntoEnumIterator;
+use termion::event::Key;
+
+type Keybinds = HashMap<InputMode, ModeKeybinds>;
+type ModeKeybinds = HashMap<Key, Action>;
+
+/// Populate the default hashmap of keybinds
+/// @@@khs26 What about an input config file?
+pub fn get_default_keybinds() -> Result<Keybinds, String> {
+ let mut defaults = Keybinds::new();
+
+ for mode in InputMode::iter() {
+ defaults.insert(mode, get_defaults_for_mode(&mode)?);
+ }
+
+ Ok(defaults)
+}
+
+fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
+ let mut defaults = ModeKeybinds::new();
+
+ match *mode {
+ InputMode::Normal => {
+ defaults.insert(Key::Ctrl('g'), Action::SwitchToMode(InputMode::Command));
+ }
+ InputMode::Command => {
+ defaults.insert(Key::Char('r'), Action::SwitchToMode(InputMode::Resize));
+ defaults.insert(Key::Char('p'), Action::SwitchToMode(InputMode::Pane));
+ defaults.insert(Key::Char('t'), Action::SwitchToMode(InputMode::Tab));
+ defaults.insert(Key::Char('s'), Action::SwitchToMode(InputMode::Scroll));
+ defaults.insert(Key::Ctrl('g'), Action::TogglePersistentMode);
+ defaults.insert(Key::Esc, Action::SwitchToMode(InputMode::Normal));
+ defaults.insert(Key::Char('q'), Action::Quit);
+ }
+ InputMode::Resize => {
+ defaults.insert(Key::Char('h'), Action::Resize(Direction::Left));
+ defaults.insert(Key::Char('j'), Action::Resize(Direction::Down));
+ defaults.insert(Key::Char('k'), Action::Resize(Direction::Up));
+ defaults.insert(Key::Char('l'), Action::Resize(Direction::Right));
+
+ defaults.insert(Key::Left, Action::Resize(Direction::Left));
+ defaults.insert(Key::Down, Action::Resize(Direction::Down));
+ defaults.insert(Key::Up, Action::Resize(Direction::Up));
+ defaults.insert(Key::Right, Action::Resize(Direction::Right));
+
+ defaults.insert(Key::Ctrl('b'), Action::Resize(Direction::Left));
+ defaults.insert(Key::Ctrl('n'), Action::Resize(Direction::Down));
+ defaults.insert(Key::Ctrl('p'), Action::Resize(Direction::Up));
+ defaults.insert(Key::Ctrl('f'), Action::Resize(Direction::Right));
+
+ defaults.insert(Key::Char('q'), Action::Quit);
+ defaults.insert(Key::Ctrl('g'), Action::TogglePersistentMode);
+ defaults.insert(Key::Esc, Action::SwitchToMode(InputMode::Normal));
+ }
+ InputMode::Pane => {
+ defaults.insert(Key::Char('h'), Action::MoveFocus(Direction::Left));
+ defaults.insert(Key::Char('j'), Action::MoveFocus(Direction::Down));
+ defaults.insert(Key::Char('k'), Action::MoveFocus(Direction::Up));
+ defaults.insert(Key::Char('l'), Action::MoveFocus(Direction::Right));
+
+ defaults.insert(Key::Left, Action::MoveFocus(Direction::Left));
+ defaults.insert(Key::Down, Action::MoveFocus(Direction::Down));
+ defaults.insert(Key::Up, Action::MoveFocus(Direction::Up));
+ defaults.insert(Key::Right, Action::MoveFocus(Direction::Right));
+
+ defaults.insert(Key::Ctrl('b'), Action::MoveFocus(Direction::Left));
+ defaults.insert(Key::Ctrl('n'), Action::MoveFocus(Direction::Down));
+ defaults.insert(Key::Ctrl('p'), Action::MoveFocus(Direction::Up));
+ defaults.insert(Key::Ctrl('f'), Action::MoveFocus(Direction::Right));
+
+ defaults.insert(Key::Char('p'), Action::SwitchFocus(Direction::Right));
+ defaults.insert(Key::Char('n'), Action::NewPane(None));
+ defaults.insert(Key::Char('d'), Action::NewPane(Some(Direction::Down)));
+ defaults.insert(Key::Char('r'), Action::NewPane(Some(Direction::Right)));
+ defaults.insert(Key::Char('x'), Action::CloseFocus);
+
+ defaults.insert(Key::Char('f'), Action::ToggleFocusFullscreen);
+
+ defaults.insert(Key::Char('q'), Action::Quit);
+ defaults.insert(Key::Ctrl('g'), Action::TogglePersistentMode);
+ defaults.insert(Key::Esc, Action::SwitchToMode(InputMode::Normal));
+ }
+ InputMode::Tab => {
+ defaults.insert(Key::Char('h'), Action::GoToPreviousTab);
+ defaults.insert(Key::Char('j'), Action::GoToNextTab);
+ defaults.insert(Key::Char('k'), Action::GoToPreviousTab);
+ defaults.insert(Key::Char('l'), Action::GoToNextTab);
+
+ defaults.insert(Key::Left, Action::GoToPreviousTab);
+ defaults.insert(Key::Down, Action::GoToNextTab);
+ defaults.insert(Key::Up, Action::GoToPreviousTab);
+ defaults.insert(Key::Right, Action::GoToNextTab);
+
+ defaults.insert(Key::Ctrl('b'), Action::GoToPreviousTab);
+ defaults.insert(Key::Ctrl('n'), Action::GoToNextTab);
+ defaults.insert(Key::Ctrl('p'), Action::GoToPreviousTab);
+ defaults.insert(Key::Ctrl('f'), Action::GoToNextTab);
+
+ defaults.insert(Key::Char('n'), Action::NewTab);
+ defaults.insert(Key::Char('x'), Action::CloseTab);
+
+ defaults.insert(Key::Char('q'), Action::Quit);
+ defaults.insert(Key::Ctrl('g'), Action::TogglePersistentMode);
+ defaults.insert(Key::Esc, Action::SwitchToMode(InputMode::Normal));
+ }
+ InputMode::Scroll => {
+ defaults.insert(Key::Char('j'), Action::ScrollDown);
+ defaults.insert(Key::Char('k'), Action::ScrollUp);
+
+ defaults.insert(Key::Down, Action::ScrollDown);
+ defaults.insert(Key::Up, Action::ScrollUp);
+
+ defaults.insert(Key::Ctrl('n'), Action::ScrollDown);
+ defaults.insert(Key::Ctrl('p'), Action::ScrollUp);
+
+ defaults.insert(Key::Char('q'), Action::Quit);
+ defaults.insert(Key::Ctrl('g'), Action::TogglePersistentMode);
+ defaults.insert(Key::Esc, Action::SwitchToMode(InputMode::Normal));
+ }
+ InputMode::Exiting => {}
+ }
+
+ Ok(defaults)
+}
+
+pub fn key_to_action(key: &Key, input: Vec<u8>, mode: &InputMode, keybinds: &Keybinds) -> Action {
+ if let Some(mode_keybinds) = keybinds.get(mode) {
+ mode_keybinds
+ .get(key)
+ .cloned()
+ .unwrap_or(Action::Write(input))
+ } else {
+ // Unrecognized mode - panic?
+ panic!("Unrecognized mode: {:?}", mode);
+ }
+}
diff --git a/src/common/input/mod.rs b/src/common/input/mod.rs
new file mode 100644
index 00000000..094054e8
--- /dev/null
+++ b/src/common/input/mod.rs
@@ -0,0 +1,3 @@
+pub mod actions;
+pub mod handler;
+pub mod keybinds;
diff --git a/src/common/ipc.rs b/src/common/ipc.rs
new file mode 100644
index 00000000..425d81d6
--- /dev/null
+++ b/src/common/ipc.rs
@@ -0,0 +1,46 @@
+// IPC stuff for starting to split things into a client and server model
+use serde::{Deserialize, Serialize};
+use std::collections::HashSet;
+
+type SessionID = u64;
+
+#[derive(PartialEq, Eq, Serialize, Deserialize, Hash)]
+pub struct Session {
+ // Unique ID for this session
+ id: SessionID,
+ // Identifier for the underlying IPC primitive (socket, pipe)
+ conn_name: String,
+ // User configured alias for the session
+ alias: String,
+}
+
+// How do we want to connect to a session?
+#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum ClientType {
+ Reader,
+ Writer,
+}
+
+// Types of messages sent from the client to the server
+#[derive(Serialize, Deserialize)]
+pub enum ClientToServerMsg {
+ // List which sessions are available
+ ListSessions,
+ // Create a new session
+ CreateSession,
+ // Attach to a running session
+ AttachToSession(SessionID, ClientType),
+ // Force detach
+ DetachSession(SessionID),
+ // Disconnect from the session we're connected to
+ DisconnectFromSession,
+}
+
+// Types of messages sent from the server to the client
+// @@@ Implement Serialize and Deserialize for this...
+pub enum ServerToClientMsg {
+ // Info about a particular session
+ SessionInfo(Session),
+ // A list of sessions
+ SessionList(HashSet<Session>),
+}
diff --git a/src/common/mod.rs b/src/common/mod.rs
new file mode 100644
index 00000000..de0b9f76
--- /dev/null
+++ b/src/common/mod.rs
@@ -0,0 +1,659 @@
+pub mod command_is_executing;
+pub mod errors;
+pub mod input;
+pub mod ipc;
+pub mod os_input_output;
+pub mod pty_bus;
+pub mod screen;
+pub mod utils;
+pub mod wasm_vm;
+
+use std::io::Write;
+use std::path::{Path, PathBuf};
+use std::sync::mpsc::{channel, sync_channel, Receiver, SendError, Sender, SyncSender};
+use std::thread;
+use std::{cell::RefCell, sync::mpsc::TrySendError};
+use std::{collections::HashMap, fs};
+
+use crate::panes::PaneId;
+use directories_next::ProjectDirs;
+use input::handler::InputState;
+use serde::{Deserialize, Serialize};
+use termion::input::TermRead;
+use wasm_vm::PluginEnv;
+use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
+use wasmer_wasi::{Pipe, WasiState};
+
+use crate::cli::CliArgs;
+use crate::layout::Layout;
+use command_is_executing::CommandIsExecuting;
+use errors::{AppContext, ContextType, ErrorContext, PluginContext, PtyContext, ScreenContext};
+use input::handler::input_loop;
+use os_input_output::OsApi;
+use pty_bus::{PtyBus, PtyInstruction};
+use screen::{Screen, ScreenInstruction};
+use utils::consts::{MOSAIC_IPC_PIPE, MOSAIC_ROOT_PLUGIN_DIR};
+use wasm_vm::{mosaic_imports, wasi_stdout, wasi_write_string, PluginInstruction};
+
+#[derive(Serialize, Deserialize, Debug)]
+pub enum ApiCommand {
+ OpenFile(PathBuf),
+ SplitHorizontally,
+ SplitVertically,
+ MoveFocus,
+}
+// FIXME: It would be good to add some more things to this over time
+#[derive(Debug, Clone, Default)]
+pub struct AppState {
+ pub input_state: InputState,
+}
+
+// FIXME: Make this a method on the big `Communication` struct, so that app_tx can be extracted
+// from self instead of being explicitly passed here
+pub fn update_state(
+ app_tx: &SenderWithContext<AppInstruction>,
+ update_fn: impl FnOnce(AppState) -> AppState,
+) {
+ let (state_tx, state_rx) = channel();
+
+ drop(app_tx.send(AppInstruction::GetState(state_tx)));
+ let state = state_rx.recv().unwrap();
+
+ drop(app_tx.send(AppInstruction::SetState(update_fn(state))))
+}
+
+pub type ChannelWithContext<T> = (Sender<(T, ErrorContext)>, Receiver<(T, ErrorContext)>);
+pub type SyncChannelWithContext<T> = (SyncSender<(T, ErrorContext)>, Receiver<(T, ErrorContext)>);
+
+#[derive(Clone)]
+enum SenderType<T: Clone> {
+ Sender(Sender<(T, ErrorContext)>),
+ SyncSender(SyncSender<(T, ErrorContext)>),
+}
+
+#[derive(Clone)]
+pub struct SenderWithContext<T: Clone> {
+ err_ctx: ErrorContext,
+ sender: SenderType<T>,
+}
+
+impl<T: Clone> SenderWithContext<T> {
+ fn new(err_ctx: ErrorContext, sender: SenderType<T>) -> Self {
+ Self { err_ctx, sender }
+ }
+
+ pub fn send(&self, event: T) -> Result<(), SendError<(T, ErrorContext)>> {
+ match self.sender {
+ SenderType::Sender(ref s) => s.send((event, self.err_ctx)),
+ SenderType::SyncSender(ref s) => s.send((event, self.err_ctx)),
+ }
+ }
+
+ pub fn try_send(&self, event: T) -> Result<(), TrySendError<(T, ErrorContext)>> {
+ if let SenderType::SyncSender(ref s) = self.sender {
+ s.try_send((event, self.err_ctx))
+ } else {
+ panic!("try_send can only be called on SyncSenders!")
+ }
+ }
+
+ pub fn update(&mut self, new_ctx: ErrorContext) {
+ self.err_ctx = new_ctx;
+ }
+}
+
+unsafe impl<T: Clone> Send for SenderWithContext<T> {}
+unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
+
+thread_local!(static OPENCALLS: RefCell<ErrorContext> = RefCell::default());
+
+#[derive(Clone)]
+pub enum AppInstruction {
+ GetState(Sender<AppState>),
+ SetState(AppState),
+ Exit,
+ Error(String),
+}
+
+pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
+ let take_snapshot = "\u{1b}[?1049h";
+ os_input.unset_raw_mode(0);
+ let _ = os_input
+ .get_stdout_writer()
+ .write(take_snapshot.as_bytes())
+ .unwrap();
+ let mut app_state = AppState::default();
+
+ let command_is_executing = CommandIsExecuting::new();
+
+ let full_screen_ws = os_input.get_terminal_size_using_fd(0);
+ os_input.set_raw_mode(0);
+ let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
+ ScreenInstruction,
+ > = channel();
+ let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
+ let mut send_screen_instructions =
+ SenderWithContext::new(err_ctx, SenderType::Sender(send_screen_instructions));
+
+ let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
+ channel();
+ let mut send_pty_instructions =
+ SenderWithContext::new(err_ctx, SenderType::Sender(send_pty_instructions));
+
+ let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
+ PluginInstruction,
+ > = channel();
+ let send_plugin_instructions =
+ SenderWithContext::new(err_ctx, SenderType::Sender(send_plugin_instructions));
+
+ let (send_app_instructions, receive_app_instructions): SyncChannelWithContext<AppInstruction> =
+ sync_channel(0);
+ let send_app_instructions =
+ SenderWithContext::new(err_ctx, SenderType::SyncSender(send_app_instructions));
+
+ let mut pty_bus = PtyBus::new(
+ receive_pty_instructions,
+ send_screen_instructions.clone(),
+ send_plugin_instructions.clone(),
+ os_input.clone(),
+ opts.debug,
+ );
+
+ // Don't use default layouts in tests, but do everywhere else
+ #[cfg(not(test))]
+ let default_layout = Some(PathBuf::from("default"));
+ #[cfg(test)]
+ let default_layout = None;
+ let maybe_layout = opts.layout.or(default_layout).map(Layout::new);
+
+ #[cfg(not(test))]
+ std::panic::set_hook({
+ use crate::errors::handle_panic;
+ let send_app_instructions = send_app_instructions.clone();
+ Box::new(move |info| {
+ handle_panic(info, &send_app_instructions);
+ })
+ });
+
+ let pty_thread = thread::Builder::new()
+ .name("pty".to_string())
+ .spawn({
+ let mut command_is_executing = command_is_executing.clone();
+ send_pty_instructions.send(PtyInstruction::NewTab).unwrap();
+ move || loop {
+ let (event, mut err_ctx) = pty_bus
+ .receive_pty_instructions
+ .recv()
+ .expect("failed to receive event on channel");
+ err_ctx.add_call(ContextType::Pty(PtyContext::from(&event)));
+ pty_bus.send_screen_instructions.update(err_ctx);
+ match event {
+ PtyInstruction::SpawnTerminal(file_to_open) => {
+ let pid = pty_bus.spawn_terminal(file_to_open);
+ pty_bus
+ .send_screen_instructions
+ .send(ScreenInstruction::NewPane(PaneId::Terminal(pid)))
+ .unwrap();
+ }
+ PtyInstruction::SpawnTerminalVertically(file_to_open) => {
+ let pid = pty_bus.spawn_terminal(file_to_open);
+ pty_bus
+ .send_screen_instructions
+ .send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid)))
+ .unwrap();
+ }
+ PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
+ let pid = pty_bus.spawn_terminal(file_to_open);
+ pty_bus
+ .send_screen_instructions
+ .send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
+ .unwrap();
+ }
+ PtyInstruction::NewTab => {
+ if let Some(layout) = maybe_layout.clone() {
+ pty_bus.spawn_terminals_for_layout(layout);
+ } else {
+ let pid = pty_bus.spawn_terminal(None);
+ pty_bus
+ .send_screen_instructions
+ .send(ScreenInstruction::NewTab(pid))
+ .unwrap();
+ }
+ }
+ PtyInstruction::ClosePane(id) => {
+ pty_bus.close_pane(id);
+ command_is_executing.done_closing_pane();
+ }
+ PtyInstruction::CloseTab(ids) => {
+ pty_bus.close_tab(ids);
+ command_is_executing.done_closing_pane();
+ }
+ PtyInstruction::Quit => {
+ break;
+ }
+ }
+ }
+ })
+ .unwrap();
+
+ let screen_thread = thread::Builder::new()
+ .name("screen".to_string())
+ .spawn({
+ let mut command_is_executing = command_is_executing.clone();
+ let os_input = os_input.clone();
+ let send_pty_instructions = send_pty_instructions.clone();
+ let send_plugin_instructions = send_plugin_instructions.clone();
+ let send_app_instructions = send_app_instructions.clone();
+ let max_panes = opts.max_panes;
+
+ move || {
+ let mut screen = Screen::new(
+ receive_screen_instructions,
+ send_pty_instructions,
+ send_plugin_instructions,
+ send_app_instructions,
+ &full_screen_ws,
+ os_input,
+ max_panes,
+ );
+ loop {
+ let (event, mut err_ctx) = screen
+ .receiver
+ .recv()
+ .expect("failed to receive event on channel");
+ err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
+ screen.send_app_instructions.update(err_ctx);
+ screen.send_pty_instructions.update(err_ctx);
+ match event {
+ ScreenInstruction::Pty(pid, vte_event) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .handle_pty_event(pid, vte_event);
+ }
+ ScreenInstruction::Render => {
+ screen.render();
+ }
+ ScreenInstruction::NewPane(pid) => {
+ screen.get_active_tab_mut().unwrap().new_pane(pid);
+ command_is_executing.done_opening_new_pane();
+ }
+ ScreenInstruction::HorizontalSplit(pid) => {
+ screen.get_active_tab_mut().unwrap().horizontal_split(pid);
+ command_is_executing.done_opening_new_pane();
+ }
+ ScreenInstruction::VerticalSplit(pid) => {
+ screen.get_active_tab_mut().unwrap().vertical_split(pid);
+ command_is_executing.done_opening_new_pane();
+ }
+ ScreenInstruction::WriteCharacter(bytes) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .write_to_active_terminal(bytes);
+ }
+ ScreenInstruction::ResizeLeft => {
+ screen.get_active_tab_mut().unwrap().resize_left();
+ }
+ ScreenInstruction::ResizeRight => {
+ screen.get_active_tab_mut().unwrap().resize_right();
+ }
+ ScreenInstruction::ResizeDown => {
+ screen.get_active_tab_mut().unwrap().resize_down();
+ }
+ ScreenInstruction::ResizeUp => {
+ screen.get_active_tab_mut().unwrap().resize_up();
+ }
+ ScreenInstruction::MoveFocus => {
+ screen.get_active_tab_mut().unwrap().move_focus();
+ }
+ ScreenInstruction::MoveFocusLeft => {
+ screen.get_active_tab_mut().unwrap().move_focus_left();
+ }
+ ScreenInstruction::MoveFocusDown => {
+ screen.get_active_tab_mut().unwrap().move_focus_down();
+ }
+ ScreenInstruction::MoveFocusRight => {
+ screen.get_active_tab_mut().unwrap().move_focus_right();
+ }
+ ScreenInstruction::MoveFocusUp => {
+ screen.get_active_tab_mut().unwrap().move_focus_up();
+ }
+ ScreenInstruction::ScrollUp => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .scroll_active_terminal_up();
+ }
+ ScreenInstruction::ScrollDown => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .scroll_active_terminal_down();
+ }
+ ScreenInstruction::ClearScroll => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .clear_active_terminal_scroll();
+ }
+ ScreenInstruction::CloseFocusedPane => {
+ screen.get_active_tab_mut().unwrap().close_focused_pane();
+ screen.render();
+ }
+ ScreenInstruction::SetSelectable(id, selectable) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .set_pane_selectable(id, selectable);
+ // FIXME: Is this needed?
+ screen.render();
+ }
+ ScreenInstruction::SetMaxHeight(id, max_height) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .set_pane_max_height(id, max_height);
+ }
+ ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .set_pane_invisible_borders(id, invisible_borders);
+ screen.render();
+ }
+ ScreenInstruction::ClosePane(id) => {
+ screen.get_active_tab_mut().unwrap().close_pane(id);
+ screen.render();
+ }
+ ScreenInstruction::ToggleActiveTerminalFullscreen => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .toggle_active_pane_fullscreen();
+ }
+ ScreenInstruction::NewTab(pane_id) => {
+ screen.new_tab(pane_id);
+ command_is_executing.done_opening_new_pane();
+ }
+ ScreenInstruction::SwitchTabNext => screen.switch_tab_next(),
+ ScreenInstruction::SwitchTabPrev => screen.switch_tab_prev(),
+ ScreenInstruction::CloseTab => screen.close_tab(),
+ ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => {
+ screen.apply_layout(layout, new_pane_pids);
+ command_is_executing.done_opening_new_pane();
+ }
+ ScreenInstruction::Quit => {
+ break;
+ }
+ }
+ }
+ }
+ })
+ .unwrap();
+
+ let wasm_thread = thread::Builder::new()
+ .name("wasm".to_string())
+ .spawn({
+ let mut send_pty_instructions = send_pty_instructions.clone();
+ let mut send_screen_instructions = send_screen_instructions.clone();
+ let mut send_app_instructions = send_app_instructions.clone();
+
+ let store = Store::default();
+ let mut plugin_id = 0;
+ let mut plugin_map = HashMap::new();
+
+ move || loop {
+ let (event, mut err_ctx) = receive_plugin_instructions
+ .recv()
+ .expect("failed to receive event on channel");
+ err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
+ send_screen_instructions.update(err_ctx);
+ send_pty_instructions.update(err_ctx);
+ send_app_instructions.update(err_ctx);
+ match event {
+ PluginInstruction::Load(pid_tx, path) => {
+ let project_dirs =
+ ProjectDirs::from("org", "Mosaic Contributors", "Mosaic").unwrap();
+ let plugin_dir = project_dirs.data_dir().join("plugins/");
+ let root_plugin_dir = Path::new(MOSAIC_ROOT_PLUGIN_DIR);
+ let wasm_bytes = fs::read(&path)
+ .or_else(|_| fs::read(&path.with_extension("wasm")))
+ .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
+ .or_else(|_| {
+ fs::read(&root_plugin_dir.join(&path).with_extension("wasm"))
+ })
+ .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
+
+ // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
+ let module = Module::new(&store, &wasm_bytes).unwrap();
+
+ let output = Pipe::new();
+ let input = Pipe::new();
+ let mut wasi_env = WasiState::new("mosaic")
+ .env("CLICOLOR_FORCE", "1")
+ .preopen(|p| {
+ p.directory(".") // FIXME: Change this to a more meaningful dir
+ .alias(".")
+ .read(true)
+ .write(true)
+ .create(true)
+ })
+ .unwrap()
+ .stdin(Box::new(input))
+ .stdout(Box::new(output))
+ .finalize()
+ .unwrap();
+
+ let wasi = wasi_env.import_object(&module).unwrap();
+
+ let plugin_env = PluginEnv {
+ plugin_id,
+ send_pty_instructions: send_pty_instructions.clone(),
+ send_screen_instructions: send_screen_instructions.clone(),
+ send_app_instructions: send_app_instructions.clone(),
+ wasi_env,
+ };
+
+ let mosaic = mosaic_imports(&store, &plugin_env);
+ let instance = Instance::new(&module, &mosaic.chain_back(wasi)).unwrap();
+
+ let start = instance.exports.get_function("_start").unwrap();
+
+ // This eventually calls the `.init()` method
+ start.call(&[]).unwrap();
+
+ plugin_map.insert(plugin_id, (instance, plugin_env));
+ pid_tx.send(plugin_id).unwrap();
+ plugin_id += 1;
+ }
+ PluginInstruction::Draw(buf_tx, pid, rows, cols) => {
+ let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
+
+ let draw = instance.exports.get_function("draw").unwrap();
+
+ draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
+ .unwrap();
+
+ buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap();
+ }
+ // FIXME: Deduplicate this with the callback below!
+ PluginInstruction::Input(pid, input_bytes) => {
+ let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
+
+ let handle_key = instance.exports.get_function("handle_key").unwrap();
+ for key in input_bytes.keys() {
+ if let Ok(key) = key {
+ wasi_write_string(
+ &plugin_env.wasi_env,
+ &serde_json::to_string(&key).unwrap(),
+ );
+ handle_key.call(&[]).unwrap();
+ }
+ }
+
+ drop(send_screen_instructions.send(ScreenInstruction::Render));
+ }
+ PluginInstruction::GlobalInput(input_bytes) => {
+ // FIXME: Set up an event subscription system, and timed callbacks
+ for (instance, plugin_env) in plugin_map.values() {
+ let handler =
+ instance.exports.get_function("handle_global_key").unwrap();
+ for key in input_bytes.keys() {
+ if let Ok(key) = key {
+ wasi_write_string(
+ &plugin_env.wasi_env,
+ &serde_json::to_string(&key).unwrap(),
+ );
+ handler.call(&[]).unwrap();
+ }
+ }
+ }
+
+ drop(send_screen_instructions.send(ScreenInstruction::Render));
+ }
+ PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
+ PluginInstruction::Quit => break,
+ }
+ }
+ })
+ .unwrap();
+
+ // TODO: currently we don't wait for this to quit
+ // because otherwise the app will hang. Need to fix this so it both
+ // listens to the ipc-bus and is able to quit cleanly
+ #[cfg(not(test))]
+ let _ipc_thread = thread::Builder::new()
+ .name("ipc_server".to_string())
+ .spawn({
+ use std::io::Read;
+ let mut send_pty_instructions = send_pty_instructions.clone();
+ let mut send_screen_instructions = send_screen_instructions.clone();
+ move || {
+ std::fs::remove_file(MOSAIC_IPC_PIPE).ok();
+ let listener = std::os::unix::net::UnixListener::bind(MOSAIC_IPC_PIPE)
+ .expect("could not listen on ipc socket");
+ let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
+ err_ctx.add_call(ContextType::IPCServer);
+ send_pty_instructions.update(err_ctx);
+ send_screen_instructions.update(err_ctx);
+
+ for stream in listener.incoming() {
+ match stream {
+ Ok(mut stream) => {
+ let mut buffer = [0; 65535]; // TODO: more accurate
+ let _ = stream
+ .read(&mut buffer)
+ .expect("failed to parse ipc message");
+ let decoded: ApiCommand = bincode::deserialize(&buffer)
+ .expect("failed to deserialize ipc message");
+ match &decoded {
+ ApiCommand::OpenFile(file_name) => {
+ let path = PathBuf::from(file_name);
+ send_pty_instructions
+ .send(PtyInstruction::SpawnTerminal(Some(path)))
+ .unwrap();
+ }
+ ApiCommand::SplitHorizontally => {
+ send_pty_instructions
+ .send(PtyInstruction::SpawnTerminalHorizontally(None))
+ .unwrap();
+ }
+ ApiCommand::SplitVertically => {
+ send_pty_instructions
+ .send(PtyInstruction::SpawnTerminalVertically(None))
+ .unwrap();
+ }
+ ApiCommand::MoveFocus => {
+ send_screen_instructions
+ .send(ScreenInstruction::MoveFocus)
+ .unwrap();
+ }
+ }
+ }
+ Err(err) => {
+ panic!("err {:?}", err);
+ }
+ }
+ }
+ }
+ })
+ .unwrap();
+
+ let _stdin_thread = thread::Builder::new()
+ .name("stdin_handler".to_string())
+ .spawn({
+ let send_screen_instructions = send_screen_instructions.clone();
+ let send_pty_instructions = send_pty_instructions.clone();
+ let send_plugin_instructions = send_plugin_instructions.clone();
+ let os_input = os_input.clone();
+ move || {
+ input_loop(
+ os_input,
+ command_is_executing,
+ send_screen_instructions,
+ send_pty_instructions,
+ send_plugin_instructions,
+ send_app_instructions,
+ )
+ }
+ });
+
+ #[warn(clippy::never_loop)]
+ loop {
+ let (app_instruction, mut err_ctx) = receive_app_instructions
+ .recv()
+ .expect("failed to receive app instruction on channel");
+
+ err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction)));
+ send_screen_instructions.update(err_ctx);
+ send_pty_instructions.update(err_ctx);
+ match app_instruction {
+ AppInstruction::GetState(state_tx) => drop(state_tx.send(app_state.clone())),
+ AppInstruction::SetState(state) => app_state = state,
+ AppInstruction::Exit => {
+ break;
+ }
+ AppInstruction::Error(backtrace) => {
+ let _ = send_screen_instructions.send(ScreenInstruction::Quit);
+ let _ = screen_thread.join();
+ let _ = send_pty_instructions.send(PtyInstruction::Quit);
+ let _ = pty_thread.join();
+ let _ = send_plugin_instructions.send(PluginInstruction::Quit);
+ let _ = wasm_thread.join();
+ os_input.unset_raw_mode(0);
+ let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
+ let error = format!("{}\n{}", goto_start_of_last_line, backtrace);
+ let _ = os_input
+ .get_stdout_writer()
+ .write(error.as_bytes())
+ .unwrap();
+ std::process::exit(1);
+ }
+ }
+ }
+
+ let _ = send_pty_instructions.send(PtyInstruction::Quit);
+ pty_thread.join().unwrap();
+ let _ = send_screen_instructions.send(ScreenInstruction::Quit);
+ screen_thread.join().unwrap();
+ let _ = send_plugin_instructions.send(PluginInstruction::Quit);
+ wasm_thread.join().unwrap();
+
+ // cleanup();
+ let reset_style = "\u{1b}[m";
+ let show_cursor = "\u{1b}[?25h";
+ let restore_snapshot = "\u{1b}[?1049l";
+ let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
+ let goodbye_message = format!(
+ "{}\n{}{}{}Bye from Mosaic!",
+ goto_start_of_last_line, restore_snapshot, reset_style, show_cursor
+ );
+
+ os_input.unset_raw_mode(0);
+ let _ = os_input
+ .get_stdout_writer()
+ .write(goodbye_message.as_bytes())
+ .unwrap();
+ os_input.get_stdout_writer().flush().unwrap();
+}
diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs
new file mode 100644
index 00000000..41ee3b3c
--- /dev/null
+++ b/src/common/os_input_output.rs
@@ -0,0 +1,217 @@
+use crate::panes::PositionAndSize;
+use nix::fcntl::{fcntl, FcntlArg, OFlag};
+use nix::pty::{forkpty, Winsize};
+use nix::sys::signal::{kill, Signal};
+use nix::sys::termios::{cfmakeraw, tcdrain, tcgetattr, tcsetattr, SetArg, Termios};
+use nix::sys::wait::waitpid;
+use nix::unistd::{read, write, ForkResult, Pid};
+use std::io::prelude::*;
+use std::io::{stdin, Write};
+use std::os::unix::io::RawFd;
+use std::path::PathBuf;
+use std::process::{Child, Command};
+use std::sync::{Arc, Mutex};
+
+use std::env;
+
+fn into_raw_mode(pid: RawFd) {
+ let mut tio = tcgetattr(pid).expect("could not get terminal attribute");
+ cfmakeraw(&mut tio);
+ match tcsetattr(pid, SetArg::TCSANOW, &tio) {
+ Ok(_) => {}
+ Err(e) => panic!("error {:?}", e),
+ };
+}
+
+fn unset_raw_mode(pid: RawFd, orig_termios: Termios) {
+ match tcsetattr(pid, SetArg::TCSANOW, &orig_termios) {
+ Ok(_) => {}
+ Err(e) => panic!("error {:?}", e),
+ };
+}
+
+pub fn get_terminal_size_using_fd(fd: RawFd) -> PositionAndSize {
+ // TODO: do this with the nix ioctl
+ use libc::ioctl;
+ use libc::TIOCGWINSZ;
+
+ let mut winsize = Winsize {
+ ws_row: 0,
+ ws_col: 0,
+ ws_xpixel: 0,
+ ws_ypixel: 0,
+ };
+
+ unsafe { ioctl(fd, TIOCGWINSZ, &mut winsize) };
+ PositionAndSize::from(winsize)
+}
+
+pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
+ // TODO: do this with the nix ioctl
+ use libc::ioctl;
+ use libc::TIOCSWINSZ;
+
+ let winsize = Winsize {
+ ws_col: columns,
+ ws_row: rows,
+ ws_xpixel: 0,
+ ws_ypixel: 0,
+ };
+ unsafe { ioctl(fd, TIOCSWINSZ, &winsize) };
+}
+
+fn handle_command_exit(mut child: Child) {
+ let signals = ::signal_hook::iterator::Signals::new(&[::signal_hook::SIGINT]).unwrap();
+ 'handle_exit: loop {
+ match child.try_wait() {
+ Ok(Some(_status)) => {
+ // TODO: handle errors?
+ break;
+ }
+ Ok(None) => {
+ ::std::thread::sleep(::std::time::Duration::from_millis(100));
+ }
+ Err(e) => panic!("error attempting to wait: {}", e),
+ }
+
+ for signal in signals.pending() {
+ // FIXME: We need to handle more signals here!
+ match signal {
+ signal_hook::SIGINT => {
+ child.kill().unwrap();
+ child.wait().unwrap();
+ break 'handle_exit;
+ }
+ _ => {}
+ }
+ }
+ }
+}
+
+fn spawn_terminal(file_to_open: Option<PathBuf>, orig_termios: Termios) -> (RawFd, RawFd) {
+ let (pid_primary, pid_secondary): (RawFd, RawFd) = {
+ match forkpty(None, Some(&orig_termios)) {
+ Ok(fork_pty_res) => {
+ let pid_primary = fork_pty_res.master;
+ let pid_secondary = match fork_pty_res.fork_result {
+ ForkResult::Parent { child } => {
+ // fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl");
+ fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK))
+ .expect("could not fcntl");
+ child
+ }
+ ForkResult::Child => match file_to_open {
+ Some(file_to_open) => {
+ if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() {
+ panic!("Can't edit files if an editor is not defined. To fix: define the EDITOR or VISUAL environment variables with the path to your editor (eg. /usr/bin/vim)");
+ }
+ let editor =
+ env::var("EDITOR").unwrap_or_else(|_| env::var("VISUAL").unwrap());
+
+ let child = Command::new(editor)
+ .args(&[file_to_open])
+ .spawn()
+ .expect("failed to spawn");
+ handle_command_exit(child);
+ ::std::process::exit(0);
+ }
+ None => {
+ let child = Command::new(env::var("SHELL").unwrap())
+ .spawn()
+ .expect("failed to spawn");
+ handle_command_exit(child);
+ ::std::process::exit(0);
+ }
+ },
+ };
+ (pid_primary, pid_secondary.as_raw())
+ }
+ Err(e) => {
+ panic!("failed to fork {:?}", e);
+ }
+ }
+ };
+ (pid_primary, pid_secondary)
+}
+
+#[derive(Clone)]
+pub struct OsInputOutput {
+ orig_termios: Arc<Mutex<Termios>>,
+}
+
+pub trait OsApi: Send + Sync {
+ fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize;
+ fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16);
+ fn set_raw_mode(&mut self, pid: RawFd);
+ fn unset_raw_mode(&mut self, pid: RawFd);
+ fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd);
+ fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
+ fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
+ fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error>;
+ fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error>;
+ fn read_from_stdin(&self) -> Vec<u8>;
+ fn get_stdout_writer(&self) -> Box<dyn Write>;
+ fn box_clone(&self) -> Box<dyn OsApi>;
+}
+
+impl OsApi for OsInputOutput {
+ fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize {
+ get_terminal_size_using_fd(pid)
+ }
+ fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16) {
+ set_terminal_size_using_fd(pid, cols, rows);
+ }
+ fn set_raw_mode(&mut self, pid: RawFd) {
+ into_raw_mode(pid);
+ }
+ fn unset_raw_mode(&mut self, pid: RawFd) {
+ let orig_termios = self.orig_termios.lock().unwrap();
+ unset_raw_mode(pid, orig_termios.clone());
+ }
+ fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
+ let orig_termios = self.orig_termios.lock().unwrap();
+ spawn_terminal(file_to_open, orig_termios.clone())
+ }
+ fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
+ read(pid, buf)
+ }
+ fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
+ write(pid, buf)
+ }
+ fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error> {
+ tcdrain(pid)
+ }
+ fn box_clone(&self) -> Box<dyn OsApi> {
+ Box::new((*self).clone())
+ }
+ fn read_from_stdin(&self) -> Vec<u8> {
+ let stdin = stdin();
+ let mut stdin = stdin.lock();
+ let buffer = stdin.fill_buf().unwrap();
+ let length = buffer.len();
+ let read_bytes = Vec::from(buffer);
+ stdin.consume(length);
+ read_bytes
+ }
+ fn get_stdout_writer(&self) -> Box<dyn Write> {
+ let stdout = ::std::io::stdout();
+ Box::new(stdout)
+ }
+ fn kill(&mut self, fd: RawFd) -> Result<(), nix::Error> {
+ kill(Pid::from_raw(fd), Some(Signal::SIGINT)).unwrap();
+ waitpid(Pid::from_raw(fd), None).unwrap();
+ Ok(())
+ }
+}
+
+impl Clone for Box<dyn OsApi> {
+ fn clone(&self) -> Box<dyn OsApi> {
+ self.box_clone()
+ }
+}
+
+pub fn get_os_input() -> OsInputOutput {
+ let current_termios = tcgetattr(0).unwrap();
+ let orig_termios = Arc::new(Mutex::new(current_termios));
+ OsInputOutput { orig_termios }
+}
diff --git a/src/common/pty_bus.rs b/src/common/pty_bus.rs
new file mode 100644
index 00000000..9a00d1de
--- /dev/null
+++ b/src/common/pty_bus.rs
@@ -0,0 +1,326 @@
+use ::async_std::stream::*;
+use ::async_std::task;
+use ::async_std::task::*;
+use ::std::collections::HashMap;
+use ::std::os::unix::io::RawFd;
+use ::std::pin::*;
+use ::std::sync::mpsc::Receiver;
+use ::std::time::{Duration, Instant};
+use ::vte;
+use std::path::PathBuf;
+
+use super::{ScreenInstruction, SenderWithContext, OPENCALLS};
+use crate::os_input_output::OsApi;
+use crate::utils::logging::debug_to_file;
+use crate::{
+ errors::{ContextType, ErrorContext},
+ panes::PaneId,
+};
+use crate::{layout::Layout, wasm_vm::PluginInstruction};
+
+pub struct ReadFromPid {
+ pid: RawFd,
+ os_input: Box<dyn OsApi>,
+}
+
+impl ReadFromPid {
+ pub fn new(pid: &RawFd, os_input: Box<dyn OsApi>) -> ReadFromPid {
+ ReadFromPid {
+ pid: *pid,
+ os_input,
+ }
+ }
+}
+
+impl Stream for ReadFromPid {
+ type Item = Vec<u8>;
+ fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+ let mut read_buffer = [0; 65535];
+ let pid = self.pid;
+ let read_result = &self.os_input.read_from_tty_stdout(pid, &mut read_buffer);
+ match read_result {
+ Ok(res) => {
+ if *res == 0 {
+ // indicates end of file
+ Poll::Ready(None)
+ } else {
+ let res = Some(read_buffer[..=*res].to_vec());
+ Poll::Ready(res)
+ }
+ }
+ Err(e) => {
+ match e {
+ nix::Error::Sys(errno) => {
+ if *errno == nix::errno::Errno::EAGAIN {
+ Poll::Ready(Some(vec![])) // TODO: better with timeout waker somehow
+ } else {
+ Poll::Ready(None)
+ }
+ }
+ _ => Poll::Ready(None),
+ }
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum VteEvent {
+ // TODO: try not to allocate Vecs
+ Print(char),
+ Execute(u8), // byte
+ Hook(Vec<i64>, Vec<u8>, bool, char), // params, intermediates, ignore, char
+ Put(u8), // byte
+ Unhook,
+ OscDispatch(Vec<Vec<u8>>, bool), // params, bell_terminated
+ CsiDispatch(Vec<i64>, Vec<u8>, bool, char), // params, intermediates, ignore, char
+ EscDispatch(Vec<u8>, bool, u8), // intermediates, ignore, byte
+}
+
+struct VteEventSender {
+ id: RawFd,
+ sender: SenderWithContext<ScreenInstruction>,
+}
+
+impl VteEventSender {
+ pub fn new(id: RawFd, sender: SenderWithContext<ScreenInstruction>) -> Self {
+ VteEventSender { id, sender }
+ }
+}
+
+impl vte::Perform for VteEventSender {
+ fn print(&mut self, c: char) {
+ let _ = self
+ .sender
+ .send(ScreenInstruction::Pty(self.id, VteEvent::Print(c)));
+ }
+ fn execute(&mut self, byte: u8) {
+ let _ = self
+ .sender
+ .send(ScreenInstruction::Pty(self.id, VteEvent::Execute(byte)));
+ }
+
+ fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
+ let params = params.iter().copied().collect();
+ let intermediates = intermediates.iter().copied().collect();
+ let instruction =
+ ScreenInstruction::Pty(self.id, VteEvent::Hook(params, intermediates, ignore, c));
+ let _ = self.sender.send(instruction);
+ }
+
+ fn put(&mut self, byte: u8) {
+ let _ = self
+ .sender
+ .send(ScreenInstruction::Pty(self.id, VteEvent::Put(byte)));
+ }
+
+ fn unhook(&mut self) {
+ let _ = self
+ .sender
+ .send(ScreenInstruction::Pty(self.id, VteEvent::Unhook));
+ }
+
+ fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
+ let params = params.iter().map(|p| p.to_vec()).collect();
+ let instruction =
+ ScreenInstruction::Pty(self.id, VteEvent::OscDispatch(params, bell_terminated));
+ let _ = self.sender.send(instruction);
+ }
+
+ fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
+ let params = params.iter().copied().collect();
+ let intermediates = intermediates.iter().copied().collect();
+ let instruction = ScreenInstruction::Pty(
+ self.id,
+ VteEvent::CsiDispatch(params, intermediates, ignore, c),
+ );
+ let _ = self.sender.send(instruction);
+ }
+
+ fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
+ let intermediates = intermediates.iter().copied().collect();
+ let instruction =
+ ScreenInstruction::Pty(self.id, VteEvent::EscDispatch(intermediates, ignore, byte));
+ let _ = self.sender.send(instruction);
+ }
+}
+
+#[derive(Clone, Debug)]
+pub enum PtyInstruction {
+ SpawnTerminal(Option<PathBuf>),
+ SpawnTerminalVertically(Option<PathBuf>),
+ SpawnTerminalHorizontally(Option<PathBuf>),
+ NewTab,
+ ClosePane(PaneId),
+ CloseTab(Vec<PaneId>),
+ Quit,
+}
+
+pub struct PtyBus {
+ pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
+ pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
+ pub id_to_child_pid: HashMap<RawFd, RawFd>,
+ os_input: Box<dyn OsApi>,
+ debug_to_file: bool,
+ task_handles: HashMap<RawFd, JoinHandle<()>>,
+}
+
+fn stream_terminal_bytes(
+ pid: RawFd,
+ mut send_screen_instructions: SenderWithContext<ScreenInstruction>,
+ os_input: Box<dyn OsApi>,
+ debug: bool,
+) -> JoinHandle<()> {
+ let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
+ task::spawn({
+ async move {
+ err_ctx.add_call(ContextType::AsyncTask);
+ send_screen_instructions.update(err_ctx);
+ let mut vte_parser = vte::Parser::new();
+ let mut vte_event_sender = VteEventSender::new(pid, send_screen_instructions.clone());
+ let mut terminal_bytes = ReadFromPid::new(&pid, os_input);
+
+ let mut last_byte_receive_time: Option<Instant> = None;
+ let mut pending_render = false;
+ let max_render_pause = Duration::from_millis(30);
+
+ while let Some(bytes) = terminal_bytes.next().await {
+ let bytes_is_empty = bytes.is_empty();
+ for byte in bytes {
+ if debug {
+ debug_to_file(byte, pid).unwrap();
+ }
+ vte_parser.advance(&mut vte_event_sender, byte);
+ }
+ if !bytes_is_empty {
+ // for UX reasons, if we got something on the wire, we only send the render notice if:
+ // 1. there aren't any more bytes on the wire afterwards
+ // 2. a certain period (currently 30ms) has elapsed since the last render
+ // (otherwise if we get a large amount of data, the display would hang
+ // until it's done)
+ // 3. the stream has ended, and so we render 1 last time
+ match last_byte_receive_time.as_mut() {
+ Some(receive_time) => {
+ if receive_time.elapsed() > max_render_pause {
+ pending_render = false;
+ let _ = send_screen_instructions.send(ScreenInstruction::Render);
+ last_byte_receive_time = Some(Instant::now());
+ } else {
+ pending_render = true;
+ }
+ }
+ None => {
+ last_byte_receive_time = Some(Instant::now());
+ pending_render = true;
+ }
+ };
+ } else {
+ if pending_render {
+ pending_render = false;
+ let _ = send_screen_instructions.send(ScreenInstruction::Render);
+ }
+ last_byte_receive_time = None;
+ task::sleep(::std::time::Duration::from_millis(10)).await;
+ }
+ }
+ send_screen_instructions
+ .send(ScreenInstruction::Render)
+ .unwrap();
+ #[cfg(not(test))]
+ // this is a little hacky, and is because the tests end the file as soon as
+ // we read everything, rather than hanging until there is new data
+ // a better solution would be to fix the test fakes, but this will do for now
+ send_screen_instructions
+ .send(ScreenInstruction::ClosePane(PaneId::Terminal(pid)))
+ .unwrap();
+ }
+ })
+}
+
+impl PtyBus {
+ pub fn new(
+ receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
+ send_screen_instructions: SenderWithContext<ScreenInstruction>,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ os_input: Box<dyn OsApi>,
+ debug_to_file: bool,
+ ) -> Self {
+ PtyBus {
+ send_screen_instructions,
+ send_plugin_instructions,
+ receive_pty_instructions,
+ os_input,
+ id_to_child_pid: HashMap::new(),
+ debug_to_file,
+ task_handles: HashMap::new(),
+ }
+ }
+ pub fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> RawFd {
+ let (pid_primary, pid_secondary): (RawFd, RawFd) =
+ self.os_input.spawn_terminal(file_to_open);
+ let task_handle = stream_terminal_bytes(
+ pid_primary,
+ self.send_screen_instructions.clone(),
+ self.os_input.clone(),
+ self.debug_to_file,
+ );
+ self.task_handles.insert(pid_primary, task_handle);
+ self.id_to_child_pid.insert(pid_primary, pid_secondary);
+ pid_primary
+ }
+ pub fn spawn_terminals_for_layout(&mut self, layout: Layout) {
+ let total_panes = layout.total_terminal_panes();
+ let mut new_pane_pids = vec![];
+ for _ in 0..total_panes {
+ let (pid_primary, pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(None);
+ self.id_to_child_pid.insert(pid_primary, pid_secondary);
+ new_pane_pids.push(pid_primary);
+ }
+ self.send_screen_instructions
+ .send(ScreenInstruction::ApplyLayout((
+ layout,
+ new_pane_pids.clone(),
+ )))
+ .unwrap();
+ for id in new_pane_pids {
+ let task_handle = stream_terminal_bytes(
+ id,
+ self.send_screen_instructions.clone(),
+ self.os_input.clone(),
+ self.debug_to_file,
+ );
+ self.task_handles.insert(id, task_handle);
+ }
+ }
+ pub fn close_pane(&mut self, id: PaneId) {
+ match id {
+ PaneId::Terminal(id) => {
+ let child_pid = self.id_to_child_pid.remove(&id).unwrap();
+ let handle = self.task_handles.remove(&id).unwrap();
+ self.os_input.kill(child_pid).unwrap();
+ task::block_on(async {
+ handle.cancel().await;
+ });
+ }
+ PaneId::Plugin(pid) => drop(
+ self.send_plugin_instructions
+ .send(PluginInstruction::Unload(pid)),
+ ),
+ }
+ }
+ pub fn close_tab(&mut self, ids: Vec<PaneId>) {
+ ids.iter().for_each(|&id| {
+ self.close_pane(id);
+ });
+ }
+}
+
+impl Drop for PtyBus {
+ fn drop(&mut self) {
+ let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect();
+ for id in child_ids {
+ self.close_pane(PaneId::Terminal(id));
+ }
+ }
+}
diff --git a/src/common/screen.rs b/src/common/screen.rs
new file mode 100644
index 00000000..6ae89ddc
--- /dev/null
+++ b/src/common/screen.rs
@@ -0,0 +1,201 @@
+use std::collections::BTreeMap;
+use std::os::unix::io::RawFd;
+use std::sync::mpsc::Receiver;
+
+use super::{AppInstruction, SenderWithContext};
+use crate::os_input_output::OsApi;
+use crate::panes::PositionAndSize;
+use crate::pty_bus::{PtyInstruction, VteEvent};
+use crate::tab::Tab;
+use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
+use crate::{layout::Layout, panes::PaneId};
+
+/*
+ * Screen
+ *
+ * this holds multiple tabs, each one holding multiple panes
+ * it tracks the active tab and controls tab switching, all the rest
+ * is performed in Tab
+ *
+ */
+
+#[derive(Debug, Clone)]
+pub enum ScreenInstruction {
+ Pty(RawFd, VteEvent),
+ Render,
+ NewPane(PaneId),
+ HorizontalSplit(PaneId),
+ VerticalSplit(PaneId),
+ WriteCharacter(Vec<u8>),
+ ResizeLeft,
+ ResizeRight,
+ ResizeDown,
+ ResizeUp,
+ MoveFocus,
+ MoveFocusLeft,
+ MoveFocusDown,
+ MoveFocusUp,
+ MoveFocusRight,
+ Quit,
+ ScrollUp,
+ ScrollDown,
+ ClearScroll,
+ CloseFocusedPane,
+ ToggleActiveTerminalFullscreen,
+ SetSelectable(PaneId, bool),
+ SetMaxHeight(PaneId, usize),
+ SetInvisibleBorders(PaneId, bool),
+ ClosePane(PaneId),
+ ApplyLayout((Layout, Vec<RawFd>)),
+ NewTab(RawFd),
+ SwitchTabNext,
+ SwitchTabPrev,
+ CloseTab,
+}
+
+pub struct Screen {
+ pub receiver: Receiver<(ScreenInstruction, ErrorContext)>,
+ max_panes: Option<usize>,
+ tabs: BTreeMap<usize, Tab>,
+ pub send_pty_instructions: SenderWithContext<PtyInstruction>,
+ pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ pub send_app_instructions: SenderWithContext<AppInstruction>,
+ full_screen_ws: PositionAndSize,
+ active_tab_index: Option<usize>,
+ os_api: Box<dyn OsApi>,
+}
+
+impl Screen {
+ pub fn new(
+ receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>,
+ send_pty_instructions: SenderWithContext<PtyInstruction>,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ send_app_instructions: SenderWithContext<AppInstruction>,
+ full_screen_ws: &PositionAndSize,
+ os_api: Box<dyn OsApi>,
+ max_panes: Option<usize>,
+ ) -> Self {
+ Screen {
+ receiver: receive_screen_instructions,
+ max_panes,
+ send_pty_instructions,
+ send_plugin_instructions,
+ send_app_instructions,
+ full_screen_ws: *full_screen_ws,
+ active_tab_index: None,
+ tabs: BTreeMap::new(),
+ os_api,
+ }
+ }
+ pub fn new_tab(&mut self, pane_id: RawFd) {
+ let tab_index = self.get_next_tab_index();
+ let tab = Tab::new(
+ tab_index,
+ &self.full_screen_ws,
+ self.os_api.clone(),
+ self.send_pty_instructions.clone(),
+ self.send_plugin_instructions.clone(),
+ self.send_app_instructions.clone(),
+ self.max_panes,
+ Some(PaneId::Terminal(pane_id)),
+ );
+ self.active_tab_index = Some(tab_index);
+ self.tabs.insert(tab_index, tab);
+ self.render();
+ }
+ fn get_next_tab_index(&self) -> usize {
+ if let Some(index) = self.tabs.keys().last() {
+ *index + 1
+ } else {
+ 0
+ }
+ }
+ pub fn switch_tab_next(&mut self) {
+ let active_tab_id = self.get_active_tab().unwrap().index;
+ let tab_ids: Vec<usize> = self.tabs.keys().copied().collect();
+ let first_tab = tab_ids.get(0).unwrap();
+ let active_tab_id_position = tab_ids.iter().position(|id| id == &active_tab_id).unwrap();
+ if let Some(next_tab) = tab_ids.get(active_tab_id_position + 1) {
+ self.active_tab_index = Some(*next_tab);
+ } else {
+ self.active_tab_index = Some(*first_tab);
+ }
+ self.render();
+ }
+ pub fn switch_tab_prev(&mut self) {
+ let active_tab_id = self.get_active_tab().unwrap().index;
+ let tab_ids: Vec<usize> = self.tabs.keys().copied().collect();
+ let first_tab = tab_ids.get(0).unwrap();
+ let last_tab = tab_ids.last().unwrap();
+
+ let active_tab_id_position = tab_ids.iter().position(|id| id == &active_tab_id).unwrap();
+ if active_tab_id == *first_tab {
+ self.active_tab_index = Some(*last_tab)
+ } else if let Some(prev_tab) = tab_ids.get(active_tab_id_position - 1) {
+ self.active_tab_index = Some(*prev_tab)
+ }
+ self.render();
+ }
+ pub fn close_tab(&mut self) {
+ let active_tab_index = self.active_tab_index.unwrap();
+ if self.tabs.len() > 1 {
+ self.switch_tab_prev();
+ }
+ let active_tab = self.tabs.remove(&active_tab_index).unwrap();
+ let pane_ids = active_tab.get_pane_ids();
+ // below we don't check the result of sending the CloseTab instruction to the pty thread
+ // because this might be happening when the app is closing, at which point the pty thread
+ // has already closed and this would result in an error
+ let _ = self
+ .send_pty_instructions
+ .send(PtyInstruction::CloseTab(pane_ids));
+ if self.tabs.is_empty() {
+ self.active_tab_index = None;
+ self.send_app_instructions
+ .send(AppInstruction::Exit)
+ .unwrap();
+ }
+ }
+ pub fn render(&mut self) {
+ if let Some(active_tab) = self.get_active_tab_mut() {
+ if active_tab.get_active_pane().is_some() {
+ active_tab.render();
+ } else {
+ self.close_tab();
+ }
+ };
+ }
+
+ pub fn get_active_tab(&self) -> Option<&Tab> {
+ match self.active_tab_index {
+ Some(tab) => self.tabs.get(&tab),
+ None => None,
+ }
+ }
+ pub fn get_tabs_mut(&mut self) -> &mut BTreeMap<usize, Tab> {
+ &mut self.tabs
+ }
+ pub fn get_active_tab_mut(&mut self) -> Option<&mut Tab> {
+ let tab = match self.active_tab_index {
+ Some(tab) => self.get_tabs_mut().get_mut(&tab),
+ None => None,
+ };
+ tab
+ }
+ pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>) {
+ let tab_index = self.get_next_tab_index();
+ let mut tab = Tab::new(
+ tab_index,
+ &self.full_screen_ws,
+ self.os_api.clone(),
+ self.send_pty_instructions.clone(),
+ self.send_plugin_instructions.clone(),
+ self.send_app_instructions.clone(),
+ self.max_panes,
+ None,
+ );
+ tab.apply_layout(layout, new_pids);
+ self.active_tab_index = Some(tab_index);
+ self.tabs.insert(tab_index, tab);
+ }
+}
diff --git a/src/common/utils/consts.rs b/src/common/utils/consts.rs
new file mode 100644
index 00000000..7a0c9ddf
--- /dev/null
+++ b/src/common/utils/consts.rs
@@ -0,0 +1,6 @@
+pub const MOSAIC_TMP_DIR: &str = "/tmp/mosaic";
+pub const MOSAIC_TMP_LOG_DIR: &str = "/tmp/mosaic/mosaic-log";
+pub const MOSAIC_TMP_LOG_FILE: &str = "/tmp/mosaic/mosaic-log/log.txt";
+pub const MOSAIC_IPC_PIPE: &str = "/tmp/mosaic/ipc";
+pub const MOSAIC_ROOT_PLUGIN_DIR: &str = "/usr/share/mosaic/plugins";
+pub const MOSAIC_ROOT_LAYOUT_DIR: &str = "/usr/share/mosaic/layouts";
diff --git a/src/common/utils/logging.rs b/src/common/utils/logging.rs
new file mode 100644
index 00000000..05af455c
--- /dev/null
+++ b/src/common/utils/logging.rs
@@ -0,0 +1,76 @@
+use std::{
+ fs,
+ io::{self, prelude::*},
+ os::unix::io::RawFd,
+ path::PathBuf,
+};
+
+use crate::utils::consts::{MOSAIC_TMP_LOG_DIR, MOSAIC_TMP_LOG_FILE};
+
+pub fn atomic_create_file(file_name: &str) {
+ let _ = fs::OpenOptions::new().create(true).open(file_name);
+}
+
+pub fn atomic_create_dir(dir_name: &str) -> io::Result<()> {
+ if let Err(e) = fs::create_dir(dir_name) {
+ if e.kind() == std::io::ErrorKind::AlreadyExists {
+ Ok(())
+ } else {
+ Err(e)
+ }
+ } else {
+ Ok(())
+ }
+}
+
+pub fn debug_log_to_file(mut message: String) -> io::Result<()> {
+ message.push('\n');
+ debug_log_to_file_without_newline(message)
+}
+
+pub fn debug_log_to_file_without_newline(message: String) -> io::Result<()> {
+ atomic_create_file(MOSAIC_TMP_LOG_FILE);
+ let mut file = fs::OpenOptions::new()
+ .append(true)
+ .create(true)
+ .open(MOSAIC_TMP_LOG_FILE)?;
+ file.write_all(message.as_bytes())
+}
+
+pub fn _debug_log_to_file_pid_3(message: String, pid: RawFd) -> io::Result<()> {
+ if pid == 3 {
+ debug_log_to_file(message)
+ } else {
+ Ok(())
+ }
+}
+
+#[allow(dead_code)]
+pub fn delete_log_file() -> io::Result<()> {
+ if fs::metadata(MOSAIC_TMP_LOG_FILE).is_ok() {
+ fs::remove_file(MOSAIC_TMP_LOG_FILE)
+ } else {
+ Ok(())
+ }
+}
+
+#[allow(dead_code)]
+pub fn delete_log_dir() -> io::Result<()> {
+ if fs::metadata(MOSAIC_TMP_LOG_DIR).is_ok() {
+ fs::remove_dir_all(MOSAIC_TMP_LOG_DIR)
+ } else {
+ Ok(())
+ }
+}
+
+pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> {
+ let mut path = PathBuf::new();
+ path.push(MOSAIC_TMP_LOG_DIR);
+ path.push(format!("mosaic-{}.log", pid.to_string()));
+
+ let mut file = fs::OpenOptions::new()
+ .append(true)
+ .create(true)
+ .open(path)?;
+ file.write_all(&[message])
+}
diff --git a/src/common/utils/mod.rs b/src/common/utils/mod.rs
new file mode 100644
index 00000000..0ea4c76f
--- /dev/null
+++ b/src/common/utils/mod.rs
@@ -0,0 +1,3 @@
+pub mod consts;
+pub mod logging;
+pub mod shared;
diff --git a/src/common/utils/shared.rs b/src/common/utils/shared.rs
new file mode 100644
index 00000000..1d8ca5c3
--- /dev/null
+++ b/src/common/utils/shared.rs
@@ -0,0 +1,19 @@
+use std::{iter, str::from_utf8};
+
+use strip_ansi_escapes::strip;
+
+pub fn ansi_len(s: &str) -> usize {
+ from_utf8(&strip(s.as_bytes()).unwrap())
+ .unwrap()
+ .chars()
+ .count()
+}
+
+pub fn pad_to_size(s: &str, rows: usize, columns: usize) -> String {
+ s.lines()
+ .map(|l| [l, &str::repeat(" ", columns - ansi_len(l))].concat())
+ .chain(iter::repeat(str::repeat(" ", columns)))
+ .take(rows)
+ .collect::<Vec<_>>()
+ .join("\n\r")
+}
diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs
new file mode 100644
index 00000000..c9a236e3
--- /dev/null
+++ b/src/common/wasm_vm.rs
@@ -0,0 +1,121 @@
+use std::{
+ path::PathBuf,
+ sync::mpsc::{channel, Sender},
+};
+use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
+use wasmer_wasi::WasiEnv;
+
+use crate::utils::logging::debug_log_to_file;
+
+use super::{
+ input::handler::get_help, pty_bus::PtyInstruction, screen::ScreenInstruction, AppInstruction,
+ PaneId, SenderWithContext,
+};
+
+#[derive(Clone, Debug)]
+pub enum PluginInstruction {
+ Load(Sender<u32>, PathBuf),
+ Draw(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
+ Input(u32, Vec<u8>), // plugin id, input bytes
+ GlobalInput(Vec<u8>), // input bytes
+ Unload(u32),
+ Quit,
+}
+
+#[derive(WasmerEnv, Clone)]
+pub struct PluginEnv {
+ pub plugin_id: u32,
+ pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
+ pub send_app_instructions: SenderWithContext<AppInstruction>,
+ pub send_pty_instructions: SenderWithContext<PtyInstruction>, // FIXME: This should be a big bundle of all of the channels
+ pub wasi_env: WasiEnv,
+}
+
+// Plugin API ---------------------------------------------------------------------------------------------------------
+
+pub fn mosaic_imports(store: &Store, plugin_env: &PluginEnv) -> ImportObject {
+ imports! {
+ "zellij" => {
+ "host_open_file" => Function::new_native_with_env(store, plugin_env.clone(), host_open_file),
+ "host_set_invisible_borders" => Function::new_native_with_env(store, plugin_env.clone(), host_set_invisible_borders),
+ "host_set_max_height" => Function::new_native_with_env(store, plugin_env.clone(), host_set_max_height),
+ "host_set_selectable" => Function::new_native_with_env(store, plugin_env.clone(), host_set_selectable),
+ "host_get_help" => Function::new_native_with_env(store, plugin_env.clone(), host_get_help),
+ }
+ }
+}
+
+// FIXME: Bundle up all of the channels! Pair that with WasiEnv?
+fn host_open_file(plugin_env: &PluginEnv) {
+ let path = PathBuf::from(wasi_stdout(&plugin_env.wasi_env).lines().next().unwrap());
+ plugin_env
+ .send_pty_instructions
+ .send(PtyInstruction::SpawnTerminal(Some(path)))
+ .unwrap();
+}
+
+// FIXME: Think about these naming conventions – should everything be prefixed by 'host'?
+fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
+ let selectable = selectable != 0;
+ plugin_env
+ .send_screen_instructions
+ .send(ScreenInstruction::SetSelectable(
+ PaneId::Plugin(plugin_env.plugin_id),
+ selectable,
+ ))
+ .unwrap()
+}
+
+fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) {
+ let max_height = max_height as usize;
+ plugin_env
+ .send_screen_instructions
+ .send(ScreenInstruction::SetMaxHeight(
+ PaneId::Plugin(plugin_env.plugin_id),
+ max_height,
+ ))
+ .unwrap()
+}
+
+fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) {
+ let invisible_borders = invisible_borders != 0;
+ plugin_env
+ .send_screen_instructions
+ .send(ScreenInstruction::SetInvisibleBorders(
+ PaneId::Plugin(plugin_env.plugin_id),
+ invisible_borders,
+ ))
+ .unwrap()
+}
+
+fn host_get_help(plugin_env: &PluginEnv) {
+ let (state_tx, state_rx) = channel();
+ // FIXME: If I changed the application so that threads were sent the termination
+ // signal and joined one at a time, there would be an order to shutdown, so I
+ // could get rid of this .is_ok() check and the .try_send()
+ if plugin_env
+ .send_app_instructions
+ .try_send(AppInstruction::GetState(state_tx))
+ .is_ok()
+ {
+ let help = get_help(&state_rx.recv().unwrap().input_state);
+ wasi_write_string(&plugin_env.wasi_env, &serde_json::to_string(&help).unwrap());
+ }
+}
+
+// Helper Functions ---------------------------------------------------------------------------------------------------
+
+// FIXME: Unwrap city
+pub fn wasi_stdout(wasi_env: &WasiEnv) -> String {
+ let mut state = wasi_env.state();
+ let wasi_file = state.fs.stdout_mut().unwrap().as_mut().unwrap();
+ let mut buf = String::new();
+ wasi_file.read_to_string(&mut buf).unwrap();
+ buf
+}
+
+pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) {
+ let mut state = wasi_env.state();
+ let wasi_file = state.fs.stdin_mut().unwrap().as_mut().unwrap();
+ writeln!(wasi_file, "{}\r", buf).unwrap();
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 00000000..aea289fb
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,59 @@
+#[cfg(test)]
+mod tests;
+
+mod cli;
+mod common;
+// TODO mod server;
+mod client;
+
+use client::{boundaries, layout, panes, tab};
+use common::{
+ command_is_executing, errors, ipc, os_input_output, pty_bus, screen, start, utils, wasm_vm,
+ ApiCommand,
+};
+
+use std::io::Write;
+use std::os::unix::net::UnixStream;
+
+use structopt::StructOpt;
+
+use crate::cli::CliArgs;
+use crate::command_is_executing::CommandIsExecuting;
+use crate::os_input_output::get_os_input;
+use crate::pty_bus::VteEvent;
+use crate::utils::{
+ consts::{MOSAIC_IPC_PIPE, MOSAIC_TMP_DIR, MOSAIC_TMP_LOG_DIR},
+ logging::*,
+};
+
+pub fn main() {
+ let opts = CliArgs::from_args();
+ if let Some(split_dir) = opts.split {
+ match split_dir {
+ 'h' => {
+ let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
+ let api_command = bincode::serialize(&ApiCommand::SplitHorizontally).unwrap();
+ stream.write_all(&api_command).unwrap();
+ }
+ 'v' => {
+ let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
+ let api_command = bincode::serialize(&ApiCommand::SplitVertically).unwrap();
+ stream.write_all(&api_command).unwrap();
+ }
+ _ => {}
+ };
+ } else if opts.move_focus {
+ let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
+ let api_command = bincode::serialize(&ApiCommand::MoveFocus).unwrap();
+ stream.write_all(&api_command).unwrap();
+ } else if let Some(file_to_open) = opts.open_file {
+ let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
+ let api_command = bincode::serialize(&ApiCommand::OpenFile(file_to_open)).unwrap();
+ stream.write_all(&api_command).unwrap();
+ } else {
+ let os_input = get_os_input();
+ atomic_create_dir(MOSAIC_TMP_DIR).unwrap();
+ atomic_create_dir(MOSAIC_TMP_LOG_DIR).unwrap();
+ start(Box::new(os_input), opts);
+ }
+}
diff --git a/src/server/mod.rs b/src/server/mod.rs
new file mode 100644
index 00000000..626b4e6b
--- /dev/null
+++ b/src/server/mod.rs
@@ -0,0 +1,5 @@
+use super::super::common::{screen};
+
+pub fn start_server() {
+ // TODO
+} \ No newline at end of file
diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs
new file mode 100644
index 00000000..fa9c6f0e
--- /dev/null
+++ b/src/tests/fakes.rs
@@ -0,0 +1,205 @@
+use crate::panes::PositionAndSize;
+use std::collections::{HashMap, VecDeque};
+use std::io::Write;
+use std::os::unix::io::RawFd;
+use std::path::PathBuf;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Mutex};
+use std::time::{Duration, Instant};
+
+use crate::os_input_output::OsApi;
+use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes};
+
+const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(50);
+
+#[derive(Clone)]
+pub enum IoEvent {
+ Kill(RawFd),
+ SetTerminalSizeUsingFd(RawFd, u16, u16),
+ IntoRawMode(RawFd),
+ UnsetRawMode(RawFd),
+ TcDrain(RawFd),
+}
+
+#[derive(Clone)]
+pub struct FakeStdoutWriter {
+ output_buffer: Arc<Mutex<Vec<u8>>>,
+ pub output_frames: Arc<Mutex<Vec<Vec<u8>>>>,
+ last_snapshot_time: Arc<Mutex<Instant>>,
+}
+
+impl FakeStdoutWriter {
+ pub fn new(last_snapshot_time: Arc<Mutex<Instant>>) -> Self {
+ FakeStdoutWriter {
+ output_buffer: Arc::new(Mutex::new(Vec::new())),
+ output_frames: Arc::new(Mutex::new(Vec::new())),
+ last_snapshot_time,
+ }
+ }
+}
+
+impl Write for FakeStdoutWriter {
+ fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
+ let mut bytes_written = 0;
+ let mut output_buffer = self.output_buffer.lock().unwrap();
+ for byte in buf {
+ bytes_written += 1;
+ output_buffer.push(*byte);
+ }
+ Ok(bytes_written)
+ }
+ fn flush(&mut self) -> Result<(), std::io::Error> {
+ let mut output_buffer = self.output_buffer.lock().unwrap();
+ let mut output_frames = self.output_frames.lock().unwrap();
+ let new_frame = output_buffer.drain(..).collect();
+ output_frames.push(new_frame);
+ let mut last_snapshot_time = self.last_snapshot_time.lock().unwrap();
+ *last_snapshot_time = Instant::now();
+ Ok(())
+ }
+}
+
+#[derive(Clone)]
+pub struct FakeInputOutput {
+ read_buffers: Arc<Mutex<HashMap<RawFd, Bytes>>>,
+ input_to_add: Arc<Mutex<Option<Vec<[u8; 10]>>>>,
+ stdin_commands: Arc<Mutex<VecDeque<Vec<u8>>>>,
+ stdin_writes: Arc<Mutex<HashMap<RawFd, Vec<u8>>>>,
+ pub stdout_writer: FakeStdoutWriter, // stdout_writer.output is already an arc/mutex
+ io_events: Arc<Mutex<Vec<IoEvent>>>,
+ win_sizes: Arc<Mutex<HashMap<RawFd, PositionAndSize>>>,
+ possible_tty_inputs: HashMap<u16, Bytes>,
+ last_snapshot_time: Arc<Mutex<Instant>>,
+ started_reading_from_pty: Arc<AtomicBool>,
+}
+
+impl FakeInputOutput {
+ pub fn new(winsize: PositionAndSize) -> Self {
+ let mut win_sizes = HashMap::new();
+ let last_snapshot_time = Arc::new(Mutex::new(Instant::now()));
+ let stdout_writer = FakeStdoutWriter::new(last_snapshot_time.clone());
+ win_sizes.insert(0, winsize); // 0 is the current terminal
+ FakeInputOutput {
+ read_buffers: Arc::new(Mutex::new(HashMap::new())),
+ stdin_writes: Arc::new(Mutex::new(HashMap::new())),
+ input_to_add: Arc::new(Mutex::new(None)),
+ stdin_commands: Arc::new(Mutex::new(VecDeque::new())),
+ stdout_writer,
+ last_snapshot_time,
+ io_events: Arc::new(Mutex::new(vec![])),
+ win_sizes: Arc::new(Mutex::new(win_sizes)),
+ possible_tty_inputs: get_possible_tty_inputs(),
+ started_reading_from_pty: Arc::new(AtomicBool::new(false)),
+ }
+ }
+ pub fn with_tty_inputs(mut self, tty_inputs: HashMap<u16, Bytes>) -> Self {
+ self.possible_tty_inputs = tty_inputs;
+ self
+ }
+ pub fn add_terminal_input(&mut self, input: &[&[u8]]) {
+ let mut stdin_commands: VecDeque<Vec<u8>> = VecDeque::new();
+ for command in input.iter() {
+ stdin_commands.push_back(command.iter().copied().collect())
+ }
+ self.stdin_commands = Arc::new(Mutex::new(stdin_commands));
+ }
+ pub fn add_terminal(&mut self, fd: RawFd) {
+ self.stdin_writes.lock().unwrap().insert(fd, vec![]);
+ }
+}
+
+impl OsApi for FakeInputOutput {
+ fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize {
+ let win_sizes = self.win_sizes.lock().unwrap();
+ let winsize = win_sizes.get(&pid).unwrap();
+ *winsize
+ }
+ fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16) {
+ let terminal_input = self
+ .possible_tty_inputs
+ .get(&cols)
+ .expect(&format!("could not find input for size {:?}", cols));
+ self.read_buffers
+ .lock()
+ .unwrap()
+ .insert(pid, terminal_input.clone());
+ self.io_events
+ .lock()
+ .unwrap()
+ .push(IoEvent::SetTerminalSizeUsingFd(pid, cols, rows));
+ }
+ fn set_raw_mode(&mut self, pid: RawFd) {
+ self.io_events
+ .lock()
+ .unwrap()
+ .push(IoEvent::IntoRawMode(pid));
+ }
+ fn unset_raw_mode(&mut self, pid: RawFd) {
+ self.io_events
+ .lock()
+ .unwrap()
+ .push(IoEvent::UnsetRawMode(pid));
+ }
+ fn spawn_terminal(&mut self, _file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
+ let next_terminal_id = self.stdin_writes.lock().unwrap().keys().len() as RawFd + 1;
+ self.add_terminal(next_terminal_id);
+ (next_terminal_id as i32, next_terminal_id + 1000) // secondary number is arbitrary here
+ }
+ fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
+ let mut read_buffers = self.read_buffers.lock().unwrap();
+ let mut bytes_read = 0;
+ match read_buffers.get_mut(&pid) {
+ Some(bytes) => {
+ for i in bytes.read_position..bytes.content.len() {
+ bytes_read += 1;
+ buf[i] = bytes.content[i];
+ }
+ if bytes_read > bytes.read_position {
+ bytes.set_read_position(bytes_read);
+ }
+ self.started_reading_from_pty.store(true, Ordering::Release);
+ return Ok(bytes_read);
+ }
+ None => Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)),
+ }
+ }
+ fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
+ let mut stdin_writes = self.stdin_writes.lock().unwrap();
+ let write_buffer = stdin_writes.get_mut(&pid).unwrap();
+ let mut bytes_written = 0;
+ for byte in buf {
+ bytes_written += 1;
+ write_buffer.push(*byte);
+ }
+ Ok(bytes_written)
+ }
+ fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error> {
+ self.io_events.lock().unwrap().push(IoEvent::TcDrain(pid));
+ Ok(())
+ }
+ fn box_clone(&self) -> Box<dyn OsApi> {
+ Box::new((*self).clone())
+ }
+ fn read_from_stdin(&self) -> Vec<u8> {
+ loop {
+ let last_snapshot_time = { *self.last_snapshot_time.lock().unwrap() };
+ if last_snapshot_time.elapsed() > MIN_TIME_BETWEEN_SNAPSHOTS {
+ break;
+ } else {
+ ::std::thread::sleep(MIN_TIME_BETWEEN_SNAPSHOTS - last_snapshot_time.elapsed());
+ }
+ }
+ self.stdin_commands
+ .lock()
+ .unwrap()
+ .pop_front()
+ .unwrap_or(vec![])
+ }
+ fn get_stdout_writer(&self) -> Box<dyn Write> {
+ Box::new(self.stdout_writer.clone())
+ }
+ fn kill(&mut self, fd: RawFd) -> Result<(), nix::Error> {
+ self.io_events.lock().unwrap().push(IoEvent::Kill(fd));
+ Ok(())
+ }
+}
diff --git a/src/tests/fixtures/bash_cursor_linewrap b/src/tests/fixtures/bash_cursor_linewrap
new file mode 100644
index 00000000..2e183a1f
--- /dev/null
+++ b/src/tests/fixtures/bash_cursor_linewrap
Binary files differ
diff --git a/src/tests/fixtures/clear_scroll_region b/src/tests/fixtures/clear_scroll_region
new file mode 100644
index 00000000..d4741364
--- /dev/null
+++ b/src/tests/fixtures/clear_scroll_region
Binary files differ
diff --git a/src/tests/fixtures/emacs_longbuf_tutorial b/src/tests/fixtures/emacs_longbuf_tutorial
new file mode 100644
index 00000000..f383e6d1
--- /dev/null
+++ b/src/tests/fixtures/emacs_longbuf_tutorial
Binary files differ
diff --git a/src/tests/fixtures/fish_and_bandwhich b/src/tests/fixtures/fish_and_bandwhich
new file mode 100644
index 00000000..a94dc7ba
--- /dev/null
+++ b/src/tests/fixtures/fish_and_bandwhich
Binary files differ
diff --git a/src/tests/fixtures/fish_paste_multiline b/src/tests/fixtures/fish_paste_multiline
new file mode 100644
index 00000000..f41187f6
--- /dev/null
+++ b/src/tests/fixtures/fish_paste_multiline
Binary files differ
diff --git a/src/tests/fixtures/fish_select_tab_completion_options b/src/tests/fixtures/fish_select_tab_completion_options
new file mode 100644
index 00000000..bab8c6d4
--- /dev/null
+++ b/src/tests/fixtures/fish_select_tab_completion_options
Binary files differ
diff --git a/src/tests/fixtures/fish_tab_completion_options b/src/tests/fixtures/fish_tab_completion_options
new file mode 100644
index 00000000..f4ad186b
--- /dev/null
+++ b/src/tests/fixtures/fish_tab_completion_options
Binary files differ
diff --git a/src/tests/fixtures/git_diff_scrollup b/src/tests/fixtures/git_diff_scrollup
new file mode 100644
index 00000000..98d06baa
--- /dev/null
+++ b/src/tests/fixtures/git_diff_scrollup
Binary files differ
diff --git a/src/tests/fixtures/git_log b/src/tests/fixtures/git_log
new file mode 100644
index 00000000..9b5bca62
--- /dev/null
+++ b/src/tests/fixtures/git_log
Binary files differ
diff --git a/src/tests/fixtures/htop b/src/tests/fixtures/htop
new file mode 100644
index 00000000..37ee0777
--- /dev/null
+++ b/src/tests/fixtures/htop
Binary files differ
diff --git a/src/tests/fixtures/htop_right_scrolling b/src/tests/fixtures/htop_right_scrolling
new file mode 100644
index 00000000..f0bcfa5b
--- /dev/null
+++ b/src/tests/fixtures/htop_right_scrolling
Binary files differ
diff --git a/src/tests/fixtures/htop_scrolling b/src/tests/fixtures/htop_scrolling
new file mode 100644
index 00000000..4e0df629
--- /dev/null
+++ b/src/tests/fixtures/htop_scrolling
Binary files differ
diff --git a/src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml b/src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml
new file mode 100644
index 00000000..e1a1a607
--- /dev/null
+++ b/src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml
@@ -0,0 +1,16 @@
+---
+ direction: Horizontal
+ parts:
+ - direction: Vertical
+ parts:
+ - direction: Horizontal
+ split_size:
+ Percent: 20
+ - direction: Horizontal
+ split_size:
+ Percent: 50
+ split_size:
+ Percent: 80
+ - direction: Vertical
+ split_size:
+ Percent: 20
diff --git a/src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml b/src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml
new file mode 100644
index 00000000..33d94225
--- /dev/null
+++ b/src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml
@@ -0,0 +1,16 @@
+---
+ direction: Horizontal
+ parts:
+ - direction: Vertical
+ parts:
+ - direction: Horizontal
+ split_size:
+ Percent: 20
+ - direction: Horizontal
+ split_size:
+ Percent: 90
+ split_size:
+ Percent: 80
+ - direction: Vertical
+ split_size:
+ Percent: 20
diff --git a/src/tests/fixtures/layouts/three-panes-with-nesting.yaml b/src/tests/fixtures/layouts/three-panes-with-nesting.yaml
new file mode 100644
index 00000000..f1e0dd7e
--- /dev/null
+++ b/src/tests/fixtures/layouts/three-panes-with-nesting.yaml
@@ -0,0 +1,16 @@
+---
+direction: Horizontal
+parts:
+ - direction: Vertical
+ parts:
+ - direction: Horizontal
+ split_size:
+ Percent: 20
+ - direction: Horizontal
+ split_size:
+ Percent: 80
+ split_size:
+ Percent: 80
+ - direction: Vertical
+ split_size:
+ Percent: 20
diff --git a/src/tests/fixtures/nvim_insert b/src/tests/fixtures/nvim_insert
new file mode 100644
index 00000000..4d476275
--- /dev/null
+++ b/src/tests/fixtures/nvim_insert
Binary files differ
diff --git a/src/tests/fixtures/tab_characters b/src/tests/fixtures/tab_characters
new file mode 100644
index 00000000..cf40aac5
--- /dev/null
+++ b/src/tests/fixtures/tab_characters
Binary files differ
diff --git a/src/tests/fixtures/vim_ctrl_d b/src/tests/fixtures/vim_ctrl_d
new file mode 100644
index 00000000..1f4929b8
--- /dev/null
+++ b/src/tests/fixtures/vim_ctrl_d
Binary files differ
diff --git a/src/tests/fixtures/vim_ctrl_u b/src/tests/fixtures/vim_ctrl_u
new file mode 100644
index 00000000..063eb231
--- /dev/null
+++ b/src/tests/fixtures/vim_ctrl_u
Binary files differ
diff --git a/src/tests/fixtures/vim_overwrite b/src/tests/fixtures/vim_overwrite
new file mode 100644
index 00000000..3a804d64
--- /dev/null
+++ b/src/tests/fixtures/vim_overwrite
Binary files differ
diff --git a/src/tests/fixtures/vim_scroll_region_down b/src/tests/fixtures/vim_scroll_region_down
new file mode 100644
index 00000000..cd2061d2
--- /dev/null
+++ b/src/tests/fixtures/vim_scroll_region_down
Binary files differ
diff --git a/src/tests/integration/basic.rs b/src/tests/integration/basic.rs
new file mode 100644
index 00000000..226b08f3
--- /dev/null
+++ b/src/tests/integration/basic.rs
@@ -0,0 +1,348 @@
+use crate::panes::PositionAndSize;
+use ::insta::assert_snapshot;
+
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::commands::{
+ COMMAND_TOGGLE, ESC, PANE_MODE, QUIT, SCROLL_DOWN_IN_SCROLL_MODE, SCROLL_MODE,
+ SCROLL_UP_IN_SCROLL_MODE, SPAWN_TERMINAL_IN_PANE_MODE, SPLIT_DOWN_IN_PANE_MODE,
+ SPLIT_RIGHT_IN_PANE_MODE, TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
+};
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(fake_win_size.clone())
+}
+
+#[test]
+pub fn starts_with_one_terminal() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn split_terminals_vertically() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn split_terminals_horizontally() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn split_largest_terminal() {
+ // this finds the largest pane and splits along its longest edge (vertically or horizontally)
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
+ let fake_win_size = PositionAndSize {
+ columns: 8,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn cannot_split_terminals_horizontally_when_active_terminal_is_too_small() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 4,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn cannot_split_largest_terminal_when_there_is_no_room() {
+ let fake_win_size = PositionAndSize {
+ columns: 8,
+ rows: 4,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn scrolling_up_inside_a_pane() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &SCROLL_MODE,
+ &SCROLL_UP_IN_SCROLL_MODE,
+ &SCROLL_UP_IN_SCROLL_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn scrolling_down_inside_a_pane() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &SCROLL_MODE,
+ &SCROLL_UP_IN_SCROLL_MODE,
+ &SCROLL_UP_IN_SCROLL_MODE,
+ &SCROLL_DOWN_IN_SCROLL_MODE,
+ &SCROLL_DOWN_IN_SCROLL_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn max_panes() {
+ // with the --max-panes option, we only allow a certain amount of panes on screen
+ // simultaneously, new panes beyond this limit will close older panes on screen
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ let mut opts = CliArgs::default();
+ opts.max_panes = Some(4);
+ start(Box::new(fake_input_output.clone()), opts);
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn toggle_focused_pane_fullscreen() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &SPAWN_TERMINAL_IN_PANE_MODE,
+ &TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ let mut opts = CliArgs::default();
+ opts.max_panes = Some(4);
+ start(Box::new(fake_input_output.clone()), opts);
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/close_pane.rs b/src/tests/integration/close_pane.rs
new file mode 100644
index 00000000..be60fd7c
--- /dev/null
+++ b/src/tests/integration/close_pane.rs
@@ -0,0 +1,647 @@
+use crate::panes::PositionAndSize;
+use ::insta::assert_snapshot;
+
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ CLOSE_PANE_IN_PANE_MODE, COMMAND_TOGGLE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT,
+ RESIZE_DOWN_IN_RESIZE_MODE, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE,
+ SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(fake_win_size.clone())
+}
+
+#[test]
+pub fn close_pane_with_another_pane_above_it() {
+ // ┌───────────┐ ┌───────────┐
+ // │xxxxxxxxxxx│ │xxxxxxxxxxx│
+ // │xxxxxxxxxxx│ │xxxxxxxxxxx│
+ // ├───────────┤ ==close==> │xxxxxxxxxxx│
+ // │███████████│ │xxxxxxxxxxx│
+ // │███████████│ │xxxxxxxxxxx│
+ // └───────────┘ └───────────┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_another_pane_below_it() {
+ // ┌───────────┐ ┌───────────┐
+ // │███████████│ │xxxxxxxxxxx│
+ // │███████████│ │xxxxxxxxxxx│
+ // ├───────────┤ ==close==> │xxxxxxxxxxx│
+ // │xxxxxxxxxxx│ │xxxxxxxxxxx│
+ // │xxxxxxxxxxx│ │xxxxxxxxxxx│
+ // └───────────┘ └───────────┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_another_pane_to_the_left() {
+ // ┌─────┬─────┐ ┌──────────┐
+ // │xxxxx│█████│ │xxxxxxxxxx│
+ // │xxxxx│█████│ ==close==> │xxxxxxxxxx│
+ // │xxxxx│█████│ │xxxxxxxxxx│
+ // └─────┴─────┘ └──────────┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_another_pane_to_the_right() {
+ // ┌─────┬─────┐ ┌──────────┐
+ // │█████│xxxxx│ │xxxxxxxxxx│
+ // │█████│xxxxx│ ==close==> │xxxxxxxxxx│
+ // │█████│xxxxx│ │xxxxxxxxxx│
+ // └─────┴─────┘ └──────────┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_multiple_panes_above_it() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │xxxxx│xxxxx│ │xxxxx│xxxxx│
+ // │xxxxx│xxxxx│ │xxxxx│xxxxx│
+ // ├─────┴─────┤ ==close==> │xxxxx│xxxxx│
+ // │███████████│ │xxxxx│xxxxx│
+ // │███████████│ │xxxxx│xxxxx│
+ // └───────────┘ └─────┴─────┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_multiple_panes_below_it() {
+ // ┌───────────┐ ┌─────┬─────┐
+ // │███████████│ │xxxxx│xxxxx│
+ // │███████████│ │xxxxx│xxxxx│
+ // ├─────┬─────┤ ==close==> │xxxxx│xxxxx│
+ // │xxxxx│xxxxx│ │xxxxx│xxxxx│
+ // │xxxxx│xxxxx│ │xxxxx│xxxxx│
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_multiple_panes_to_the_left() {
+ // ┌─────┬─────┐ ┌──────────┐
+ // │xxxxx│█████│ │xxxxxxxxxx│
+ // │xxxxx│█████│ │xxxxxxxxxx│
+ // ├─────┤█████│ ==close==> ├──────────┤
+ // │xxxxx│█████│ │xxxxxxxxxx│
+ // │xxxxx│█████│ │xxxxxxxxxx│
+ // └─────┴─────┘ └──────────┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_multiple_panes_to_the_right() {
+ // ┌─────┬─────┐ ┌──────────┐
+ // │█████│xxxxx│ │xxxxxxxxxx│
+ // │█████│xxxxx│ │xxxxxxxxxx│
+ // │█████├─────┤ ==close==> ├──────────┤
+ // │█████│xxxxx│ │xxxxxxxxxx│
+ // │█████│xxxxx│ │xxxxxxxxxx│
+ // └─────┴─────┘ └──────────┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() {
+ // ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐
+ // │xxx│xxx│xxx│xxx│ │xxx│xxx│xxx│xxx│
+ // ├───┤xxx│xxx├───┤ ├───┤xxx│xxx├───┤
+ // │xxx├───┴───┤xxx│ ==close==> │xxx│xxx│xxx│xxx│
+ // │xxx│███████│xxx│ │xxx│xxx│xxx│xxx│
+ // │xxx│███████│xxx│ │xxx│xxx│xxx│xxx│
+ // └───┴───────┴───┘ └───┴───┴───┴───┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() {
+ // ┌───┬───────┬───┐ ┌───┬───┬───┬───┐
+ // │xxx│███████│xxx│ │xxx│xxx│xxx│xxx│
+ // │xxx│███████│xxx│ │xxx│xxx│xxx│xxx│
+ // │xxx├───┬───┤xxx│ ==close==> │xxx│xxx│xxx│xxx│
+ // ├───┤xxx│xxx├───┤ ├───┤xxx│xxx├───┤
+ // │xxx│xxx│xxx│xxx│ │xxx│xxx│xxx│xxx│
+ // └───┴───┴───┴───┘ └───┴───┴───┴───┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() {
+ // ┌────┬──────┐ ┌────┬──────┐
+ // │xxxx│xxxxxx│ │xxxx│xxxxxx│
+ // ├────┴┬─────┤ ├────┴──────┤
+ // │xxxxx│█████│ │xxxxxxxxxxx│
+ // ├─────┤█████│ ==close==> ├───────────┤
+ // │xxxxx│█████│ │xxxxxxxxxxx│
+ // ├────┬┴─────┤ ├────┬──────┤
+ // │xxxx│xxxxxx│ │xxxx│xxxxxx│
+ // └────┴──────┘ └────┴──────┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 30,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() {
+ // ┌────┬──────┐ ┌────┬──────┐
+ // │xxxx│xxxxxx│ │xxxx│xxxxxx│
+ // ├────┴┬─────┤ ├────┴──────┤
+ // │█████│xxxxx│ │xxxxxxxxxxx│
+ // │█████├─────┤ ==close==> ├───────────┤
+ // │█████│xxxxx│ │xxxxxxxxxxx│
+ // ├────┬┴─────┤ ├────┬──────┤
+ // │xxxx│xxxxxx│ │xxxx│xxxxxx│
+ // └────┴──────┘ └────┴──────┘
+ // █ == pane being closed
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 30,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn closing_last_pane_exits_app() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/compatibility.rs b/src/tests/integration/compatibility.rs
new file mode 100644
index 00000000..84deea5e
--- /dev/null
+++ b/src/tests/integration/compatibility.rs
@@ -0,0 +1,486 @@
+use ::insta::assert_snapshot;
+use ::std::collections::HashMap;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::possible_tty_inputs::Bytes;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{COMMAND_TOGGLE, QUIT};
+
+/*
+ * These tests are general compatibility tests for non-trivial scenarios running in the terminal.
+ * They use fake TTY input replicated from these scenarios (and so don't actually interact with the
+ * OS).
+ *
+ * They work like this:
+ * - receive fake TTY input containing various VTE instructions.
+ * - run that output through mosaic so it interprets it and creates its state based on it
+ * - read that state into a Human-readable snapshot and compare it to the expected snapshot for
+ * this scenario.
+ *
+ */
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize, fixture_name: &str) -> FakeInputOutput {
+ let mut tty_inputs = HashMap::new();
+ let fixture_bytes = Bytes::from_file_in_fixtures(&fixture_name);
+ tty_inputs.insert(fake_win_size.columns as u16, fixture_bytes);
+ FakeInputOutput::new(fake_win_size.clone()).with_tty_inputs(tty_inputs)
+}
+
+#[test]
+pub fn run_bandwhich_from_fish_shell() {
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "fish_and_bandwhich";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn fish_tab_completion_options() {
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "fish_tab_completion_options";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn fish_select_tab_completion_options() {
+ // the difference between this and the previous test is that here we press <TAB>
+ // twice, meaning the selection moves between the options and the command line
+ // changes.
+ // this is not clearly seen in the snapshot because it does not include styles,
+ // but we can see the command line change and the cursor staying in place
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "fish_select_tab_completion_options";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn vim_scroll_region_down() {
+ // here we test a case where vim defines the scroll region as lesser than the screen row count
+ // and then scrolls down
+ // the region is defined here by vim as 1-26 (there are 28 rows)
+ // then the cursor is moved to line 26 and a new line is added
+ // what should happen is that the first line in the scroll region (1) is deleted
+ // and an empty line is inserted in the last scroll region line (26)
+ // this tests also has other steps afterwards that fills the line with the next line in the
+ // file
+ // experience appear to the user
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "vim_scroll_region_down";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]); // quit (ctrl-q)
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn vim_ctrl_d() {
+ // in vim ctrl-d moves down half a page
+ // in this case, it sends the terminal the csi 'M' directive, which tells it to delete X (13 in
+ // this case) lines inside the scroll region and push the other lines up
+ // what happens here is that 13 lines are deleted and instead 13 empty lines are added at the
+ // end of the scroll region
+ // vim makes sure to fill these empty lines with the rest of the file
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "vim_ctrl_d";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn vim_ctrl_u() {
+ // in vim ctrl-u moves up half a page
+ // in this case, it sends the terminal the csi 'L' directive, which tells it to insert X (13 in
+ // this case) lines at the cursor, pushing away (deleting) the last line in the scroll region
+ // this causes the effect of scrolling up X lines (vim replaces the lines with the ones in the
+ // file above the current content)
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "vim_ctrl_u";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn htop() {
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "htop";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn htop_scrolling() {
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "htop_scrolling";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn htop_right_scrolling() {
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "htop_right_scrolling";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn vim_overwrite() {
+ // this tests the vim overwrite message
+ // to recreate:
+ // * open a file in vim
+ // * open the same file in another window
+ // * change the file in the other window and save
+ // * change the file in the original vim window and save
+ // * confirm you would like to change the file by pressing 'y' and then ENTER
+ // * if everything looks fine, this test passed :)
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "vim_overwrite";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn clear_scroll_region() {
+ // this tests the scroll region used by eg. vim is cleared properly
+ // this means that when vim exits, we get back the previous scroll
+ // buffer
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "clear_scroll_region";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn display_tab_characters_properly() {
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "tab_characters";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn neovim_insert_mode() {
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "nvim_insert";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn bash_cursor_linewrap() {
+ // this test makes sure that when we enter a command that is beyond the screen border, that it
+ // immediately goes down one line
+ let fake_win_size = PositionAndSize {
+ columns: 116,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "bash_cursor_linewrap";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn fish_paste_multiline() {
+ // here we paste a multiline command in fish shell, making sure we support it
+ // going up and changing the colors of our line-wrapped pasted text
+ let fake_win_size = PositionAndSize {
+ columns: 149,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "fish_paste_multiline";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn git_log() {
+ let fake_win_size = PositionAndSize {
+ columns: 149,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "git_log";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn git_diff_scrollup() {
+ // this tests makes sure that when we have a git diff that exceeds the screen size
+ // we are able to scroll up
+ let fake_win_size = PositionAndSize {
+ columns: 149,
+ rows: 28,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "git_diff_scrollup";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn emacs_longbuf() {
+ let fake_win_size = PositionAndSize {
+ columns: 284,
+ rows: 60,
+ x: 0,
+ y: 0,
+ };
+ let fixture_name = "emacs_longbuf_tutorial";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/layouts.rs b/src/tests/integration/layouts.rs
new file mode 100644
index 00000000..2086ac72
--- /dev/null
+++ b/src/tests/integration/layouts.rs
@@ -0,0 +1,52 @@
+use insta::assert_snapshot;
+use std::path::PathBuf;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::commands::{COMMAND_TOGGLE, QUIT};
+use crate::tests::utils::get_output_frame_snapshots;
+use crate::{start, CliArgs};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(fake_win_size.clone())
+}
+
+#[test]
+pub fn accepts_basic_layout() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[&COMMAND_TOGGLE, &COMMAND_TOGGLE, &QUIT]);
+ let mut opts = CliArgs::default();
+ opts.layout = Some(PathBuf::from(
+ "src/tests/fixtures/layouts/three-panes-with-nesting.yaml",
+ ));
+
+ start(Box::new(fake_input_output.clone()), opts);
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+
+ let snapshot_count = snapshots.len();
+ let first_snapshot = snapshots.get(0).unwrap();
+ let next_to_last_snapshot = snapshots.get(snapshot_count - 2).unwrap();
+ let last_snapshot = snapshots.last().unwrap();
+ // here we only test the first, next to last and last snapshot because there's a race condition
+ // with the other snapshots. Namely all the terminals are created asynchronously and read in an
+ // async task, so we have no way to guarantee the order in which their bytes will be read, and
+ // it doesn't really matter in this context. We just want to see that the layout is initially
+ // created properly and that in the end it's populated properly with its content
+ //
+ // we read the next to last as well as the last, because the last includes the "Bye from
+ // Mosaic" message, and we also want to make sure things are fine before that
+ assert_snapshot!(first_snapshot);
+ assert_snapshot!(next_to_last_snapshot);
+ assert_snapshot!(last_snapshot);
+}
diff --git a/src/tests/integration/mod.rs b/src/tests/integration/mod.rs
new file mode 100644
index 00000000..512db9d0
--- /dev/null
+++ b/src/tests/integration/mod.rs
@@ -0,0 +1,14 @@
+pub mod basic;
+pub mod close_pane;
+pub mod compatibility;
+pub mod layouts;
+pub mod move_focus_down;
+pub mod move_focus_left;
+pub mod move_focus_right;
+pub mod move_focus_up;
+pub mod resize_down;
+pub mod resize_left;
+pub mod resize_right;
+pub mod resize_up;
+pub mod tabs;
+pub mod toggle_fullscreen;
diff --git a/src/tests/integration/move_focus_down.rs b/src/tests/integration/move_focus_down.rs
new file mode 100644
index 00000000..639078d8
--- /dev/null
+++ b/src/tests/integration/move_focus_down.rs
@@ -0,0 +1,79 @@
+use ::insta::assert_snapshot;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ COMMAND_TOGGLE, MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT,
+ SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(*fake_win_size)
+}
+
+#[test]
+pub fn move_focus_down() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_UP_IN_PANE_MODE,
+ &MOVE_FOCUS_DOWN_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn move_focus_down_to_the_largest_overlap() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_UP_IN_PANE_MODE,
+ &MOVE_FOCUS_DOWN_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/move_focus_left.rs b/src/tests/integration/move_focus_left.rs
new file mode 100644
index 00000000..a8e073b9
--- /dev/null
+++ b/src/tests/integration/move_focus_left.rs
@@ -0,0 +1,79 @@
+use ::insta::assert_snapshot;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ COMMAND_TOGGLE, MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, PANE_MODE, QUIT,
+ SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(*fake_win_size)
+}
+
+#[test]
+pub fn move_focus_left() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_LEFT_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn move_focus_left_to_the_largest_overlap() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_LEFT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_LEFT_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/move_focus_right.rs b/src/tests/integration/move_focus_right.rs
new file mode 100644
index 00000000..72c24a9b
--- /dev/null
+++ b/src/tests/integration/move_focus_right.rs
@@ -0,0 +1,79 @@
+use ::insta::assert_snapshot;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ COMMAND_TOGGLE, MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, PANE_MODE, QUIT,
+ SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(*fake_win_size)
+}
+
+#[test]
+pub fn move_focus_right() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_LEFT_IN_PANE_MODE,
+ &MOVE_FOCUS_RIGHT_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn move_focus_right_to_the_largest_overlap() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_LEFT_IN_PANE_MODE,
+ &MOVE_FOCUS_RIGHT_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/move_focus_up.rs b/src/tests/integration/move_focus_up.rs
new file mode 100644
index 00000000..fcfedb18
--- /dev/null
+++ b/src/tests/integration/move_focus_up.rs
@@ -0,0 +1,79 @@
+use ::insta::assert_snapshot;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ COMMAND_TOGGLE, MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT,
+ SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(*fake_win_size)
+}
+
+#[test]
+pub fn move_focus_up() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_UP_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn move_focus_up_to_the_largest_overlap() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_UP_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_UP_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/resize_down.rs b/src/tests/integration/resize_down.rs
new file mode 100644
index 00000000..ec0221cb
--- /dev/null
+++ b/src/tests/integration/resize_down.rs
@@ -0,0 +1,689 @@
+use insta::assert_snapshot;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ COMMAND_TOGGLE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_DOWN_IN_RESIZE_MODE,
+ RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(*fake_win_size)
+}
+
+#[test]
+pub fn resize_down_with_pane_above() {
+ // ┌───────────┐ ┌───────────┐
+ // │ │ │ │
+ // │ │ │ │
+ // ├───────────┤ ==resize=down==> │ │
+ // │███████████│ ├───────────┤
+ // │███████████│ │███████████│
+ // │███████████│ │███████████│
+ // └───────────┘ └───────────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_pane_below() {
+ // ┌───────────┐ ┌───────────┐
+ // │███████████│ │███████████│
+ // │███████████│ │███████████│
+ // ├───────────┤ ==resize=down==> │███████████│
+ // │ │ ├───────────┤
+ // │ │ │ │
+ // └───────────┘ └───────────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_panes_above_and_below() {
+ // ┌───────────┐ ┌───────────┐
+ // │ │ │ │
+ // │ │ │ │
+ // ├───────────┤ │ │
+ // │███████████│ ==resize=down==> ├───────────┤
+ // │███████████│ │███████████│
+ // ├───────────┤ ├───────────┤
+ // │ │ │ │
+ // │ │ │ │
+ // └───────────┘ └───────────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_multiple_panes_above() {
+ //
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // ├─────┴─────┤ ==resize=down==> │ │ │
+ // │███████████│ ├─────┴─────┤
+ // │███████████│ │███████████│
+ // └───────────┘ └───────────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_panes_above_aligned_left_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // │ │ │ │ │ │
+ // ├─────┼─────┤ ==resize=down==> ├─────┤ │
+ // │ │█████│ │ ├─────┤
+ // │ │█████│ │ │█████│
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_panes_below_aligned_left_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │█████│ │ │█████│
+ // │ │█████│ │ │█████│
+ // ├─────┼─────┤ ==resize=down==> ├─────┤█████│
+ // │ │ │ │ ├─────┤
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_panes_above_aligned_right_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // │ │ │ │ │ │
+ // ├─────┼─────┤ ==resize=down==> │ ├─────┤
+ // │█████│ │ ├─────┤ │
+ // │█████│ │ │█████│ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_panes_below_aligned_right_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │█████│ │ │█████│ │
+ // │█████│ │ │█████│ │
+ // ├─────┼─────┤ ==resize=down==> │█████├─────┤
+ // │ │ │ ├─────┤ │
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() {
+ // ┌───┬───┬───┐ ┌───┬───┬───┐
+ // │ │ │ │ │ │ │ │
+ // │ │ │ │ │ │ │ │
+ // ├───┼───┼───┤ ==resize=down==> ├───┤ ├───┤
+ // │ │███│ │ │ ├───┤ │
+ // │ │███│ │ │ │███│ │
+ // └───┴───┴───┘ └───┴───┴───┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() {
+ // ┌───┬───┬───┐ ┌───┬───┬───┐
+ // │ │███│ │ │ │███│ │
+ // │ │███│ │ │ │███│ │
+ // ├───┼───┼───┤ ==resize=down==> ├───┤███├───┤
+ // │ │ │ │ │ ├───┤ │
+ // │ │ │ │ │ │ │ │
+ // └───┴───┴───┘ └───┴───┴───┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_left_and_right() {
+ // ┌─┬───────┬─┐ ┌─┬───────┬─┐
+ // │ │ │ │ │ │ │ │
+ // │ │ │ │ │ │ │ │
+ // ├─┼─┬───┬─┼─┤ ==resize=down==> ├─┤ ├─┤
+ // │ │ │███│ │ │ │ ├─┬───┬─┤ │
+ // │ │ │███│ │ │ │ │ │███│ │ │
+ // └─┴─┴───┴─┴─┘ └─┴─┴───┴─┴─┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 40,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_right() {
+ // ┌─┬─┬───┬─┬─┐ ┌─┬─┬───┬─┬─┐
+ // │ │ │███│ │ │ │ │ │███│ │ │
+ // │ │ │███│ │ │ │ │ │███│ │ │
+ // ├─┼─┴───┴─┼─┤ ==resize=down==> ├─┤ │███│ ├─┤
+ // │ │ │ │ │ ├─┴───┴─┤ │
+ // │ │ │ │ │ │ │ │
+ // └─┴───────┴─┘ └─┴───────┴─┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 40,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() {
+ // ┌───────────┐ ┌───────────┐
+ // │███████████│ │███████████│
+ // ├───────────┤ ==resize=down==> ├───────────┤
+ // │ │ │ │
+ // └───────────┘ └───────────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 5,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_DOWN_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/resize_left.rs b/src/tests/integration/resize_left.rs
new file mode 100644
index 00000000..9645a4ec
--- /dev/null
+++ b/src/tests/integration/resize_left.rs
@@ -0,0 +1,670 @@
+use ::insta::assert_snapshot;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ COMMAND_TOGGLE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE,
+ RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(*fake_win_size)
+}
+
+#[test]
+pub fn resize_left_with_pane_to_the_left() {
+ // ┌─────┬─────┐ ┌───┬───────┐
+ // │ │█████│ │ │███████│
+ // │ │█████│ ==resize=left==> │ │███████│
+ // │ │█████│ │ │███████│
+ // └─────┴─────┘ └───┴───────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_pane_to_the_right() {
+ // ┌─────┬─────┐ ┌───┬───────┐
+ // │█████│ │ │███│ │
+ // │█████│ │ ==resize=left==> │███│ │
+ // │█████│ │ │███│ │
+ // └─────┴─────┘ └───┴───────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_panes_to_the_left_and_right() {
+ // ┌─────┬─────┬─────┐ ┌─────┬───┬───────┐
+ // │ │█████│ │ │ │███│ │
+ // │ │█████│ │ ==resize=left==> │ │███│ │
+ // │ │█████│ │ │ │███│ │
+ // └─────┴─────┴─────┘ └─────┴───┴───────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_multiple_panes_to_the_left() {
+ // ┌─────┬─────┐ ┌───┬───────┐
+ // │ │█████│ │ │███████│
+ // ├─────┤█████│ ==resize=left==> ├───┤███████│
+ // │ │█████│ │ │███████│
+ // └─────┴─────┘ └───┴───────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // ├─────┼─────┤ ==resize=left==> ├───┬─┴─────┤
+ // │ │█████│ │ │███████│
+ // └─────┴─────┘ └───┴───────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // ├─────┼─────┤ ==resize=left==> ├───┬─┴─────┤
+ // │█████│ │ │███│ │
+ // └─────┴─────┘ └───┴───────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() {
+ // ┌─────┬─────┐ ┌───┬───────┐
+ // │ │█████│ │ │███████│
+ // ├─────┼─────┤ ==resize=left==> ├───┴─┬─────┤
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() {
+ // ┌─────┬─────┐ ┌───┬───────┐
+ // │█████│ │ │███│ │
+ // ├─────┼─────┤ ==resize=left==> ├───┴─┬─────┤
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // ├─────┼─────┤ ├───┬─┴─────┤
+ // │ │█████│ ==resize=left==> │ │███████│
+ // ├─────┼─────┤ ├───┴─┬─────┤
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // ├─────┼─────┤ ├───┬─┴─────┤
+ // │█████│ │ ==resize=left==> │███│ │
+ // ├─────┼─────┤ ├───┴─┬─────┤
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_above_and_below() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // ├─────┼─────┤ ├───┬─┴─────┤
+ // │ ├─────┤ │ ├───────┤
+ // │ │█████│ ==resize=left==> │ │███████│
+ // │ ├─────┤ │ ├───────┤
+ // ├─────┼─────┤ ├───┴─┬─────┤
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 40,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_above_and_below() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // ├─────┼─────┤ ├───┬─┴─────┤
+ // ├─────┤ │ ├───┤ │
+ // │█████│ │ ==resize=left==> │███│ │
+ // ├─────┤ │ ├───┤ │
+ // ├─────┼─────┤ ├───┴─┬─────┤
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ // TODO: combine with above
+ columns: 121,
+ rows: 40,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn cannot_resize_left_when_pane_to_the_left_is_at_minimum_width() {
+ // ┌─┬─┐ ┌─┬─┐
+ // │ │█│ │ │█│
+ // │ │█│ ==resize=left==> │ │█│
+ // │ │█│ │ │█│
+ // └─┴─┘ └─┴─┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 9,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/resize_right.rs b/src/tests/integration/resize_right.rs
new file mode 100644
index 00000000..cea8ecda
--- /dev/null
+++ b/src/tests/integration/resize_right.rs
@@ -0,0 +1,670 @@
+use ::insta::assert_snapshot;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ COMMAND_TOGGLE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_MODE,
+ RESIZE_RIGHT_IN_RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SPLIT_DOWN_IN_PANE_MODE,
+ SPLIT_RIGHT_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(*fake_win_size)
+}
+
+#[test]
+pub fn resize_right_with_pane_to_the_left() {
+ // ┌─────┬─────┐ ┌───────┬───┐
+ // │ │█████│ │ │███│
+ // │ │█████│ ==resize=right==> │ │███│
+ // │ │█████│ │ │███│
+ // └─────┴─────┘ └───────┴───┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_pane_to_the_right() {
+ // ┌─────┬─────┐ ┌───────┬───┐
+ // │█████│ │ │███████│ │
+ // │█████│ │ ==resize=right==> │███████│ │
+ // │█████│ │ │███████│ │
+ // └─────┴─────┘ └───────┴───┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_panes_to_the_left_and_right() {
+ // ┌─────┬─────┬─────┐ ┌─────┬───────┬───┐
+ // │ │█████│ │ │ │███████│ │
+ // │ │█████│ │ ==resize=right==> │ │███████│ │
+ // │ │█████│ │ │ │███████│ │
+ // └─────┴─────┴─────┘ └─────┴───────┴───┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_multiple_panes_to_the_left() {
+ // ┌─────┬─────┐ ┌───────┬───┐
+ // │ │█████│ │ │███│
+ // ├─────┤█████│ ==resize=right==> ├───────┤███│
+ // │ │█████│ │ │███│
+ // └─────┴─────┘ └───────┴───┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // ├─────┼─────┤ ==resize=right==> ├─────┴─┬───┤
+ // │ │█████│ │ │███│
+ // └─────┴─────┘ └───────┴───┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // ├─────┼─────┤ ==resize=right==> ├─────┴─┬───┤
+ // │█████│ │ │███████│ │
+ // └─────┴─────┘ └───────┴───┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() {
+ // ┌─────┬─────┐ ┌───────┬───┐
+ // │ │█████│ │ │███│
+ // ├─────┼─────┤ ==resize=right==> ├─────┬─┴───┤
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() {
+ // ┌─────┬─────┐ ┌───────┬───┐
+ // │█████│ │ │███████│ │
+ // ├─────┼─────┤ ==resize=right==> ├─────┬─┴───┤
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // ├─────┼─────┤ ├─────┴─┬───┤
+ // │ │█████│ ==resize=right==> │ │███│
+ // ├─────┼─────┤ ├─────┬─┴───┤
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // ├─────┼─────┤ ├─────┴─┬───┤
+ // │█████│ │ ==resize=right==> │███████│ │
+ // ├─────┼─────┤ ├─────┬─┴───┤
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_above_and_below() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // ├─────┼─────┤ ├─────┴─┬───┤
+ // │ ├─────┤ │ ├───┤
+ // │ │█████│ ==resize=right==> │ │███│
+ // │ ├─────┤ │ ├───┤
+ // ├─────┼─────┤ ├─────┬─┴───┤
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 40,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_above_and_below() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // ├─────┼─────┤ ├─────┴─┬───┤
+ // ├─────┤ │ ├───────┤ │
+ // │█████│ │ ==resize=right==> │███████│ │
+ // ├─────┤ │ ├───────┤ │
+ // ├─────┼─────┤ ├─────┬─┴───┤
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 40,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn cannot_resize_right_when_pane_to_the_left_is_at_minimum_width() {
+ // ┌─┬─┐ ┌─┬─┐
+ // │ │█│ │ │█│
+ // │ │█│ ==resize=right==> │ │█│
+ // │ │█│ │ │█│
+ // └─┴─┘ └─┴─┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 9,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_RIGHT_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/resize_up.rs b/src/tests/integration/resize_up.rs
new file mode 100644
index 00000000..6e9f7813
--- /dev/null
+++ b/src/tests/integration/resize_up.rs
@@ -0,0 +1,685 @@
+use ::insta::assert_snapshot;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ COMMAND_TOGGLE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE,
+ RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(*fake_win_size)
+}
+
+#[test]
+pub fn resize_up_with_pane_above() {
+ // ┌───────────┐ ┌───────────┐
+ // │ │ │ │
+ // │ │ ├───────────┤
+ // ├───────────┤ ==resize=up==> │███████████│
+ // │███████████│ │███████████│
+ // │███████████│ │███████████│
+ // └───────────┘ └───────────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_pane_below() {
+ // ┌───────────┐ ┌───────────┐
+ // │███████████│ │███████████│
+ // │███████████│ ├───────────┤
+ // ├───────────┤ ==resize=up==> │ │
+ // │ │ │ │
+ // │ │ │ │
+ // └───────────┘ └───────────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_panes_above_and_below() {
+ // ┌───────────┐ ┌───────────┐
+ // │ │ │ │
+ // │ │ ├───────────┤
+ // ├───────────┤ │███████████│
+ // │███████████│ ==resize=up==> │███████████│
+ // │███████████│ │███████████│
+ // ├───────────┤ ├───────────┤
+ // │ │ │ │
+ // │ │ │ │
+ // └───────────┘ └───────────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_multiple_panes_above() {
+ //
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ ├─────┴─────┤
+ // ├─────┴─────┤ ==resize=up==> │███████████│
+ // │███████████│ │███████████│
+ // └───────────┘ └───────────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_panes_above_aligned_left_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ ├─────┤
+ // ├─────┼─────┤ ==resize=up==> ├─────┤█████│
+ // │ │█████│ │ │█████│
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_panes_below_aligned_left_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │█████│ │ │█████│
+ // │ │█████│ │ ├─────┤
+ // ├─────┼─────┤ ==resize=up==> ├─────┤ │
+ // │ │ │ │ │ │
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_panes_above_aligned_right_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │ │ │ │ │ │
+ // │ │ │ ├─────┤ │
+ // ├─────┼─────┤ ==resize=up==> │█████├─────┤
+ // │█████│ │ │█████│ │
+ // │█████│ │ │█████│ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_panes_below_aligned_right_with_current_pane() {
+ // ┌─────┬─────┐ ┌─────┬─────┐
+ // │█████│ │ │█████│ │
+ // │█████│ │ ├─────┤ │
+ // ├─────┼─────┤ ==resize=up==> │ ├─────┤
+ // │ │ │ │ │ │
+ // │ │ │ │ │ │
+ // └─────┴─────┘ └─────┴─────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() {
+ // ┌───┬───┬───┐ ┌───┬───┬───┐
+ // │ │ │ │ │ │ │ │
+ // │ │ │ │ │ ├───┤ │
+ // ├───┼───┼───┤ ==resize=up==> ├───┤███├───┤
+ // │ │███│ │ │ │███│ │
+ // │ │███│ │ │ │███│ │
+ // └───┴───┴───┘ └───┴───┴───┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() {
+ // ┌───┬───┬───┐ ┌───┬───┬───┐
+ // │ │███│ │ │ │███│ │
+ // │ │███│ │ │ ├───┤ │
+ // ├───┼───┼───┤ ==resize=up==> ├───┤ ├───┤
+ // │ │ │ │ │ │ │ │
+ // │ │ │ │ │ │ │ │
+ // └───┴───┴───┘ └───┴───┴───┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_and_right() {
+ // ┌─┬───────┬─┐ ┌─┬───────┬─┐
+ // │ │ │ │ │ │ │ │
+ // │ │ │ │ │ ├─┬───┬─┤ │
+ // ├─┼─┬───┬─┼─┤ ==resize=up==> ├─┤ │███│ ├─┤
+ // │ │ │███│ │ │ │ │ │███│ │ │
+ // │ │ │███│ │ │ │ │ │███│ │ │
+ // └─┴─┴───┴─┴─┘ └─┴─┴───┴─┴─┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 40,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_right() {
+ // ┌─┬─┬───┬─┬─┐ ┌─┬─┬───┬─┬─┐
+ // │ │ │███│ │ │ │ │ │███│ │ │
+ // │ │ │███│ │ │ │ ├─┴───┴─┤ │
+ // ├─┼─┴───┴─┼─┤ ==resize=up==> ├─┤ ├─┤
+ // │ │ │ │ │ │ │ │
+ // │ │ │ │ │ │ │ │
+ // └─┴───────┴─┘ └─┴───────┴─┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 40,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_LEFT_IN_RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn cannot_resize_up_when_pane_above_is_at_minimum_height() {
+ // ┌───────────┐ ┌───────────┐
+ // │ │ │ │
+ // ├───────────┤ ==resize=up==> ├───────────┤
+ // │███████████│ │███████████│
+ // └───────────┘ └───────────┘
+ // █ == focused pane
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 5,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &RESIZE_MODE,
+ &RESIZE_UP_IN_RESIZE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_largest_terminal_when_there_is_no_room.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_largest_terminal_when_there_is_no_room.snap
new file mode 100644
index 00000000..d6b74e46
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_largest_terminal_when_there_is_no_room.snap
@@ -0,0 +1,9 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+line18-b
+line19-b
+prompt $
+ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_terminals_horizontally_when_active_terminal_is_too_small.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_terminals_horizontally_when_active_terminal_is_too_small.snap
new file mode 100644
index 00000000..7eaac7a0
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_terminals_horizontally_when_active_terminal_is_too_small.snap
@@ -0,0 +1,9 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap
new file mode 100644
index 00000000..c9f9eb10
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+line2-bb
+line3-bb
+line4-bb
+line5-bb
+line6-bb
+line7-bb
+line8-bb
+line9-bb
+line10-b
+line11-b
+line12-b
+line13-b
+line14-b
+line15-b
+line16-b
+line17-b
+line18-b
+line19-b
+prompt $
+ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__max_panes.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__max_panes.snap
new file mode 100644
index 00000000..2f2d2faf
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__max_panes.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__scrolling_down_inside_a_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__scrolling_down_inside_a_pane.snap
new file mode 100644
index 00000000..8f4ec6e2
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__scrolling_down_inside_a_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+────────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__scrolling_up_inside_a_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__scrolling_up_inside_a_pane.snap
new file mode 100644
index 00000000..485bde1e
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__scrolling_up_inside_a_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+────────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line18-bb█bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__split_largest_terminal.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__split_largest_terminal.snap
new file mode 100644
index 00000000..2f2d2faf
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__split_largest_terminal.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__split_terminals_horizontally.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__split_terminals_horizontally.snap
new file mode 100644
index 00000000..fab0ca8f
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__split_terminals_horizontally.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__split_terminals_vertically.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__split_terminals_vertically.snap
new file mode 100644
index 00000000..aaae6b5a
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__split_terminals_vertically.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+a │line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__starts_with_one_terminal.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__starts_with_one_terminal.snap
new file mode 100644
index 00000000..1eec02f4
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__starts_with_one_terminal.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__toggle_focused_pane_fullscreen.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__toggle_focused_pane_fullscreen.snap
new file mode 100644
index 00000000..868e5a95
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__toggle_focused_pane_fullscreen.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/basic.rs
+expression: snapshot_before_quit
+
+---
+line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_above_it.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_above_it.snap
new file mode 100644
index 00000000..8ba40bc7
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_above_it.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_below_it.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_below_it.snap
new file mode 100644
index 00000000..8ba40bc7
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_below_it.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_to_the_left.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_to_the_left.snap
new file mode 100644
index 00000000..8ba40bc7
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_to_the_left.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_to_the_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_to_the_right.snap
new file mode 100644
index 00000000..cf7bacf6
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_another_pane_to_the_right.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_above_it.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_above_it.snap
new file mode 100644
index 00000000..05bbb477
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_above_it.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+a │line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_above_it_away_from_screen_edges.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_above_it_away_from_screen_edges.snap
new file mode 100644
index 00000000..ffc59eb5
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_above_it_away_from_screen_edges.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+a │bbbbbbbbbbbbbbb│line1-bbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line2-bbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line3-bbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbb│line4-bbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line5-bbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line6-bbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbb│line7-bbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line17-bbbbbbbb│line8-bbbbbbbb│prompt $
+────────────────────────────────────────────────────────────┤bbbbbbbbbbbbbbb│line9-bbbbbbbb├─────────────────────────────
+a │bbbbbbbbbbbbbbb│line10-bbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line11-bbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbb│line12-bbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbb│line13-bbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line14-bbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line15-bbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbb│line16-bbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line17-bbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line18-bbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbb│line19-bbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ │prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_below_it.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_below_it.snap
new file mode 100644
index 00000000..05bbb477
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_below_it.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+a │line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_below_it_away_from_screen_edges.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_below_it_away_from_screen_edges.snap
new file mode 100644
index 00000000..6f546abf
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_below_it_away_from_screen_edges.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line1-bbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbb│line2-bbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line3-bbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbb│line4-bbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbb│line5-bbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line6-bbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line7-bbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbbbbbbb│line8-bbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line9-bbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line10-bbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbb│line11-bbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line18-bbbbbbbb│line12-bbbbbbb│prompt $
+────────────────────────────────────────────────────────────┤bbbbbbbbbbbbbbb│line13-bbbbbbb├─────────────────────────────
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line14-bbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line15-bbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbb│line16-bbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line17-bbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbb│line18-bbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbb│line19-bbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ │prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_left.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_left.snap
new file mode 100644
index 00000000..abd742bc
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_left.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_left_away_from_screen_edges.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_left_away_from_screen_edges.snap
new file mode 100644
index 00000000..eb3d6039
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_left_away_from_screen_edges.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+──────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █
+──────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────
+bbbbbbbbbb │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_right.snap
new file mode 100644
index 00000000..448a074c
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_right.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_right_away_from_screen_edges.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_right_away_from_screen_edges.snap
new file mode 100644
index 00000000..eb3d6039
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__close_pane_with_multiple_panes_to_the_right_away_from_screen_edges.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+──────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █
+──────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────
+bbbbbbbbbb │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__close_pane__closing_last_pane_exits_app.snap b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__closing_last_pane_exits_app.snap
new file mode 100644
index 00000000..cf7bacf6
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__close_pane__closing_last_pane_exits_app.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/close_pane.rs
+expression: snapshot_before_quit
+
+---
+line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__bash_cursor_linewrap.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__bash_cursor_linewrap.snap
new file mode 100644
index 00000000..d657b3e3
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__bash_cursor_linewrap.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+Welcome to fish, the friendly interactive shell
+⋊> ~/c/mosaic on main ⨯ bash 16:00:06
+[aram@green mosaic]$ 12345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345
+█
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__clear_scroll_region.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__clear_scroll_region.snap
new file mode 100644
index 00000000..2932c549
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__clear_scroll_region.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+Welcome to fish, the friendly interactive shell
+⋊> ~/c/mosaic on main ⨯ vim some-file 15:07:22
+⋊> ~/c/mosaic on main ⨯ █ 15:07:29
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__display_tab_characters_properly.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__display_tab_characters_properly.snap
new file mode 100644
index 00000000..ff52b200
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__display_tab_characters_properly.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+
+ OS: 5.9.13-arch1-1 GNU/Linux
+ Uptime: 10 hours, 42 minutes
+ Hostname: kingdom
+ Disk usage:
+
+df: /run/user/1000/doc: Operation not permitted
+ / 321G / 514G 66%
+ /efi 27M / 96M 28%
+
+ Network:
+
+ wlp2s0 192.168.0.3
+
+[I] [20:07] kingdom:mosaic (main) | █
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__emacs_longbuf.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__emacs_longbuf.snap
new file mode 100644
index 00000000..4f7e5515
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__emacs_longbuf.snap
@@ -0,0 +1,65 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+➜ mosaic git:(mosaic#130) emacs
+➜ mosaic git:(mosaic#130) emacs -nw
+➜ mosaic git:(mosaic#130) exit
+█
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_paste_multiline.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_paste_multiline.snap
new file mode 100644
index 00000000..08f59e5a
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_paste_multiline.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+
+ OS: 5.9.14-arch1-1 GNU/Linux
+ Uptime: 12 hours, 21 minutes
+ Hostname: kingdom
+ Disk usage:
+
+df: /run/user/1000/doc: Operation not permitted
+ / 295G / 514G 61%
+ /efi 27M / 96M 28%
+
+ Network:
+
+ wlp2s0 192.168.0.3
+
+[I] [21:58] kingdom:mosaic (main) | echo -ne (\
+ df -l -h | grep -E 'dev/(xvda|sd|mapper)' | \
+ awk '{printf "\\\\t%s\\\\t%4s / %4s %s\\\\n\n", $6, $3, $2, $5}' | \
+ sed -e 's/^\(.*\([8][5-9]\|[9][0-9]\)%.*\)$/\\\\e[0;31m\1\\\\e[0m/' -e 's/^\(.*\([7][5-9]\|[8][0-4]\)%.*\
+)$/\\\\e[0;33m\1\\\\e[0m/' | \
+ paste -sd ''\
+ )█
+
+
+
+
+
+
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_select_tab_completion_options.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_select_tab_completion_options.snap
new file mode 100644
index 00000000..9a29d950
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_select_tab_completion_options.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+Welcome to fish, the friendly interactive shell
+⋊> ~/c/mosaic on main ⨯ sudo badblocks █ 11:32:23
+badblocks (Executable, 33kB) base64 (Executable, 42kB) bash (Executable, 906kB)
+bandwhich (Executable, 3.0MB) basename (Executable, 38kB) bashbug (Executable, 6.8kB)
+base32 (Executable, 42kB) basenc (Executable, 50kB) bass
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_tab_completion_options.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_tab_completion_options.snap
new file mode 100644
index 00000000..006cd485
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__fish_tab_completion_options.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+Welcome to fish, the friendly interactive shell
+⋊> ~/c/mosaic on main ⨯ sudo ba█dwhich 11:18:26
+badblocks (Executable, 33kB) base64 (Executable, 42kB) bash (Executable, 906kB)
+bandwhich (Executable, 3.0MB) basename (Executable, 38kB) bashbug (Executable, 6.8kB)
+base32 (Executable, 42kB) basenc (Executable, 50kB) bass
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__git_diff_scrollup.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__git_diff_scrollup.snap
new file mode 100644
index 00000000..63123585
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__git_diff_scrollup.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+
+src/terminal_pane/scroll.rs
+───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+
+────────────────────────────────────────────────┐
+use crate::terminal_pane::terminal_character::{ │
+────────────────────────────────────────────────┘
+5
+ CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
+};
+
+use crate::utils::logging::debug_log_to_file;
+
+/*
+ * Scroll
+ *
+
+──────────────┐
+impl Scroll { │
+──────────────┘
+663
+ pub fn move_current_buffer_to_alternative_buffer(&mut self) {
+ self.alternative_buffer = Some(self.canonical_lines.drain(..).collect());
+ self.alternative_cursor_position = Some(self.cursor_position);
+ self.cursor_position.reset();
+ self.clear_all();
+ }
+:█
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__git_log.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__git_log.snap
new file mode 100644
index 00000000..74978bda
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__git_log.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+
+ * wip: doesn't render when new tab is created?
+
+ * wip: doesnt re-render when a new tab is spawned for now
+
+ * wip: tabs now are a BTreeMap and we can switch between them in both directions
+
+ * wip: I think that should also be here
+
+ * wip: cleanup
+
+ * Spawn a new terminal simultaneously with a new tab
+
+ * Ensure proper Opening and Closing of tabs
+
+ * cleanup
+
+ * more cleanup
+
+ * tests(snapshots): add 'loading' snapshot to each scenario
+
+ * fix(tests): update snapshots
+
+ * Add tests for tabs implementation
+
+ * wip: added tests, moved tab related stuff to a separate file
+
+:█
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop.snap
new file mode 100644
index 00000000..5f37e9ae
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+
+ 1 [||||| 10.1%] Tasks: 73, 413 thr; 1 running
+ 2 [||||||| 13.5%] Load average: 1.03 1.07 1.30
+ 3 [|||||| 10.8%] Uptime: 22:41:15
+ 4 [|||||| 10.6%]
+ Mem[|||||||||||||||||||||||||||||||||||||3.28G/15.3G]
+ Swp[ 0K/16.0G]
+
+ PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
+ 1352 aram 20 0 3776M 581M 238M S 8.7 3.7 2h01:10 /usr/lib/firefox/firefox
+ 98777 aram 20 0 537M 6184 4240 S 8.1 0.0 0:00.80 target/debug/mosaic --debug
+ 1669 aram 20 0 2944M 318M 130M S 8.1 2.0 1h01:33 /usr/lib/firefox/firefox -contentproc -childID 6 -i
+ 826 aram 9 -11 1581M 15092 11244 S 6.1 0.1 42:21.83 /usr/bin/pulseaudio --daemonize=no
+ 9419 aram 20 0 533M 7392 3344 S 4.7 0.0 22:01.92 /usr/local/bin/mosaic --max-panes 4
+ 98913 aram 20 0 537M 6184 4240 S 3.4 0.0 0:00.31 target/debug/mosaic --debug
+ 1505 aram 20 0 3187M 329M 206M S 3.4 2.1 23:35.90 /usr/lib/firefox/firefox -contentproc -childID 2 -i
+ 98912 aram 20 0 537M 6184 4240 S 2.7 0.0 0:00.22 target/debug/mosaic --debug
+ 1164 aram -6 0 1581M 15092 11244 S 2.7 0.1 21:39.80 /usr/bin/pulseaudio --daemonize=no
+ 1247 aram 20 0 1184M 292M 84828 S 2.7 1.9 38:01.54 /usr/lib/Xorg -nolisten tcp :0 vt1 -keeptty -auth /
+ 1475 aram -11 0 3776M 581M 238M S 2.0 3.7 14:27.94 /usr/lib/firefox/firefox
+ 8574 aram 20 0 2944M 318M 130M S 2.0 2.0 14:36.50 /usr/lib/firefox/firefox -contentproc -childID 6 -i
+ 1364 aram 20 0 3776M 581M 238M S 2.0 3.7 18:01.89 /usr/lib/firefox/firefox
+ 1870 aram 20 0 3776M 581M 238M S 2.0 3.7 13:27.06 /usr/lib/firefox/firefox
+ 9427 aram 20 0 533M 7392 3344 S 2.0 0.0 6:53.47 /usr/local/bin/mosaic --max-panes 4
+ 98905 aram 20 0 537M 6184 4240 S 2.0 0.0 0:00.17 target/debug/mosaic --debug
+ 99272 aram 20 0 8456 4348 3320 R 1.3 0.0 0:00.13 htop
+ 8611 aram 20 0 2944M 318M 130M S 1.3 2.0 8:17.90 /usr/lib/firefox/firefox -contentproc -childID 6 -i
+F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_right_scrolling.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_right_scrolling.snap
new file mode 100644
index 00000000..d14a411a
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_right_scrolling.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+
+ 1 [|||||||||| 16.9%] Tasks: 110, 512 thr; 1 running
+ 2 [|||||||||| 17.1%] Load average: 1.04 1.30 1.29
+ 3 [||||||| 13.9%] Uptime: 6 days, 07:01:39
+ 4 [||||||||| 14.9%]
+ Mem[|||||||||||||||||||||||||||||||||||||8.80G/15.3G]
+ Swp[| 2.82M/16.0G]
+
+ PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
+ 20 0 8576 4508 3356 R 1.3 0.0 0:00.11 htop
+ 20 0 171M 10868 7804 S 0.0 0.1 2:08.38 /sbin/init
+ 20 0 107M 29364 28092 S 0.0 0.2 0:03.42 /usr/lib/systemd/systemd-journald
+ 20 0 32648 9568 6616 S 0.0 0.1 0:01.87 /usr/lib/systemd/systemd-udevd
+ 20 0 78060 992 856 S 0.0 0.0 0:00.00 /usr/bin/lvmetad -f
+ 20 0 6952 4356 3492 S 0.0 0.0 0:57.67 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidf
+ 20 0 14824 7552 5984 S 0.0 0.0 0:12.93 /usr/bin/connmand -n --nodnsproxy
+ 20 0 17696 7940 6696 S 0.0 0.0 0:01.25 /usr/lib/systemd/systemd-logind
+ 20 0 1635M 56148 20460 S 0.0 0.3 1:27.24 /usr/bin/dockerd -H fd://
+ 20 0 1635M 56148 20460 S 0.0 0.3 0:38.37 /usr/bin/dockerd -H fd://
+ 20 0 1635M 56148 20460 S 0.0 0.3 0:00.01 /usr/bin/dockerd -H fd://
+ 20 0 1635M 56148 20460 S 0.0 0.3 0:00.00 /usr/bin/dockerd -H fd://
+ 20 0 1635M 56148 20460 S 0.0 0.3 0:00.00 /usr/bin/dockerd -H fd://
+ 20 0 1635M 56148 20460 S 0.0 0.3 0:00.00 /usr/bin/dockerd -H fd://
+ 20 0 1635M 56148 20460 S 0.0 0.3 1:47.55 /usr/bin/dockerd -H fd://
+ 20 0 1635M 56148 20460 S 0.0 0.3 1:26.19 /usr/bin/dockerd -H fd://
+ 20 0 1635M 56148 20460 S 0.0 0.3 1:40.77 /usr/bin/dockerd -H fd://
+ 20 0 1635M 56148 20460 S 0.0 0.3 1:47.26 /usr/bin/dockerd -H fd://
+F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_scrolling.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_scrolling.snap
new file mode 100644
index 00000000..6e34a969
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_scrolling.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+█
+ 1 [||||||||||||||||||||||||||||||||||||||||||100.0%] Tasks: 79, 382 thr; 1 running
+ 2 [ 0.0%] Load average: 1.40 1.43 1.38
+ 3 [ 0.0%] Uptime: 2 days, 07:33:50
+ 4 [ 0.0%]
+ Mem[|||||||||||||||||||||||||||||||||||||3.64G/15.3G]
+ Swp[ 0K/16.0G]
+
+ PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
+ 123934 aram 20 0 8444 4384 3364 R 66.7 0.0 0:00.05 htop --delay=100000000000
+ 1 root 20 0 171M 11616 8608 S 0.0 0.1 0:56.91 /sbin/init
+ 268 root 20 0 93324 34340 33072 S 0.0 0.2 0:01.05 /usr/lib/systemd/systemd-journald
+ 276 root 20 0 32648 10192 7240 S 0.0 0.1 0:01.13 /usr/lib/systemd/systemd-udevd
+ 286 root 20 0 78060 1132 996 S 0.0 0.0 0:00.00 /usr/bin/lvmetad -f
+ 343 dbus 20 0 6952 4384 3520 S 0.0 0.0 0:13.85 /usr/bin/dbus-daemon --system --address=systemd: --
+ 344 root 20 0 14588 7512 6176 S 0.0 0.0 0:03.21 /usr/bin/connmand -n --nodnsproxy
+ 345 root 20 0 17696 8372 7128 S 0.0 0.1 0:00.67 /usr/lib/systemd/systemd-logind
+ 395 root 20 0 1635M 83324 44976 S 0.0 0.5 0:32.43 /usr/bin/dockerd -H fd://
+ 396 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.01 /usr/bin/dockerd -H fd://
+ 397 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.01 /usr/bin/dockerd -H fd://
+ 398 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.00 /usr/bin/dockerd -H fd://
+ 399 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.00 /usr/bin/dockerd -H fd://
+ 412 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.00 /usr/bin/dockerd -H fd://
+ 441 root 20 0 1635M 83324 44976 S 0.0 0.5 0:41.35 /usr/bin/dockerd -H fd://
+ 442 root 20 0 1635M 83324 44976 S 0.0 0.5 0:33.61 /usr/bin/dockerd -H fd://
+ 444 root 20 0 1635M 83324 44976 S 0.0 0.5 0:33.13 /usr/bin/dockerd -H fd://
+ 449 root 20 0 1635M 83324 44976 S 0.0 0.5 0:41.39 /usr/bin/dockerd -H fd://
+F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__neovim_insert_mode.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__neovim_insert_mode.snap
new file mode 100644
index 00000000..159bfcac
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__neovim_insert_mode.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+line 1
+line 2
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+█ine 10
+line 11
+line 12
+line 13
+line 14
+line 15
+line 16
+line 17
+line 18
+line 19
+line 20
+line 21
+line 22
+line 23
+line 24
+line 25
+line 26
+some-file 10,1 Top
+-- INSERT --
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__run_bandwhich_from_fish_shell.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__run_bandwhich_from_fish_shell.snap
new file mode 100644
index 00000000..e250ba86
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__run_bandwhich_from_fish_shell.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+ Total Up / Down: 46Bps / 57Bps
+┌Utilization by process name───────────────────────────────────────────────────────────────────────────────────────┐
+│Process Connections Up / Down │
+│ │
+│firefox 3 46Bps / 57Bps │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
+ Press <SPACE> to pause. Use <TAB> to rearrange tables. (DNS queries hidden).
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_ctrl_d.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_ctrl_d.snap
new file mode 100644
index 00000000..5f922afd
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_ctrl_d.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+ 3 line14
+ 2 line15
+ 1 line16
+17 █ine17
+ 1 line18
+ 2 line19
+ 3 line20
+ 4 line21
+ 5 line22
+ 6 line23
+ 7 line24
+ 8 line25
+ 9 line26
+ 10 line27
+ 11 line28
+ 12 line29
+ 13 line30
+ 14 line31
+ 15 line32
+ 16 line33
+ 17 line34
+ 18 line35
+ 19 line36
+ 20 line37
+ 21 line38
+ 22 line39
+ NORMAL testfile.rs unix | utf-8 | rust 40% 17:1
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_ctrl_u.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_ctrl_u.snap
new file mode 100644
index 00000000..f0b76512
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_ctrl_u.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+ 22 line4
+ 21 line5
+ 20 line6
+ 19 line7
+ 18 line8
+ 17 line9
+ 16 line10
+ 15 line11
+ 14 line12
+ 13 line13
+ 12 line14
+ 11 line15
+ 10 line16
+ 9 line17
+ 8 line18
+ 7 line19
+ 6 line20
+ 5 line21
+ 4 line22
+ 3 line23
+ 2 line24
+ 1 line25
+26 █ine26
+ 1 line27
+ 2 line28
+ 3 line29
+ NORMAL testfile.rs unix | utf-8 | rust 61% 26:1
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_overwrite.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_overwrite.snap
new file mode 100644
index 00000000..8dee61ab
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_overwrite.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+1 █
+ 1 line 1
+ 2 line 2
+ 3 line 3
+ 4 line 4
+ 5 line 5
+ 6 line 6
+ 7 line 7
+ 8 line 8
+ 9 line 9
+ 10 line 10
+ 11 line 11
+ 12 line 12
+ 13 line 13
+ 14 line 14
+ 15 line 15
+ 16 line 16
+ 17 line 17
+ 18 line 18
+ 19 line 19
+ 20 line 20
+ 21 line 21
+ 22 line 22
+ 23 line 23
+ 24 line 24
+ 25 line 25
+ NORMAL some-file unix | utf-8 | no ft 1% 1:1
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_scroll_region_down.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_scroll_region_down.snap
new file mode 100644
index 00000000..b4a455e3
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__vim_scroll_region_down.snap
@@ -0,0 +1,33 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot_before_quit
+
+---
+ 22 line2
+ 21 line3
+ 20 line4
+ 19 line5
+ 18 line6
+ 17 line7
+ 16 line8
+ 15 line9
+ 14 line10
+ 13 line11
+ 12 line12
+ 11 line13
+ 10 line14
+ 9 line15
+ 8 line16
+ 7 line17
+ 6 line18
+ 5 line19
+ 4 line20
+ 3 line21
+ 2 line22
+ 1 line23
+24 █ine24
+ 1 line25
+ 2 line26
+ 3 line27
+ NORMAL testfile.rs unix | utf-8 | rust 57% 24:1
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__layouts__accepts_basic_layout.snap b/src/tests/integration/snapshots/zellij__tests__integration__layouts__accepts_basic_layout.snap
new file mode 100644
index 00000000..2f814621
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__layouts__accepts_basic_layout.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/layouts.rs
+expression: first_snapshot
+
+---
+█ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────
+
+
+
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__move_focus_down__move_focus_down.snap b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_down__move_focus_down.snap
new file mode 100644
index 00000000..3a89bb92
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_down__move_focus_down.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/move_focus_down.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__move_focus_down__move_focus_down_to_the_largest_overlap.snap b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_down__move_focus_down_to_the_largest_overlap.snap
new file mode 100644
index 00000000..24cdf0f5
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_down__move_focus_down_to_the_largest_overlap.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/move_focus_down.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+────────────────────────────────────────────────────────────┬──────────────────────────────┬─────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+a │line18-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left.snap b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left.snap
new file mode 100644
index 00000000..ad879d6c
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/move_focus_left.rs
+expression: snapshot_before_quit
+
+---
+a │line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left_to_the_largest_overlap.snap b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left_to_the_largest_overlap.snap
new file mode 100644
index 00000000..8e35b971
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left_to_the_largest_overlap.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/move_focus_left.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+────────────────────────────────────────────────────────────┤line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+────────────────────────────────────────────────────────────┤line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right.snap
new file mode 100644
index 00000000..fdd7c3d5
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/move_focus_right.rs
+expression: snapshot_before_quit
+
+---
+a │line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right_to_the_largest_overlap.snap b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right_to_the_largest_overlap.snap
new file mode 100644
index 00000000..0b745b04
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right_to_the_largest_overlap.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/move_focus_right.rs
+expression: snapshot_before_quit
+
+---
+a │line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │prompt $ █
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa├────────────────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $
+a ├────────────────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__move_focus_up__move_focus_up.snap b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_up__move_focus_up.snap
new file mode 100644
index 00000000..cf6fc8c8
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_up__move_focus_up.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/move_focus_up.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__move_focus_up__move_focus_up_to_the_largest_overlap.snap b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_up__move_focus_up_to_the_largest_overlap.snap
new file mode 100644
index 00000000..f09d8fa6
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_up__move_focus_up_to_the_largest_overlap.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/move_focus_up.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+a │line18-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │prompt $ │prompt $
+────────────────────────────────────────────────────────────┴──────────────────────────────┴─────────────────────────────
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__cannot_resize_down_when_pane_below_is_at_minimum_height.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__cannot_resize_down_when_pane_below_is_at_minimum_height.snap
new file mode 100644
index 00000000..523612a9
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__cannot_resize_down_when_pane_below_is_at_minimum_height.snap
@@ -0,0 +1,10 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_multiple_panes_above.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_multiple_panes_above.snap
new file mode 100644
index 00000000..b69d1c29
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_multiple_panes_above.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_pane_above.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_pane_above.snap
new file mode 100644
index 00000000..69a35e27
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_pane_above.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_pane_below.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_pane_below.snap
new file mode 100644
index 00000000..831b1c52
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_pane_below.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_and_right_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_and_right_with_current_pane.snap
new file mode 100644
index 00000000..6a5197f7
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_and_right_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line19-bbbbbbbbbbbbbbbbbbbbbbb│prompt $
+────────────────────────────────────────────────────────────┤bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb├─────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│prompt $ │line12-bbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb├──────────────────────────────┤line13-bbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_left_and_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_left_and_right.snap
new file mode 100644
index 00000000..0cd41085
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_left_and_right.snap
@@ -0,0 +1,45 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line1-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line2-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line3-bbbbbbbbbbbbbbbbbbbbbbb
+a │line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb
+a │line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaa│line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+a │line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ │prompt $
+──────────────────────────────┤ ├─────────────────────────────
+line2-bbbbbbbbbbbbbbbbbbbbbbbb│ │line2-bbbbbbbbbbbbbbbbbbbbbbb
+line3-bbbbbbbbbbbbbbbbbbbbbbbb├──────────┬──────────────────────────────────┬──────────────┤line3-bbbbbbbbbbbbbbbbbbbbbbb
+line4-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb │line4-bbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb
+line5-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb │line5-bbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb
+line6-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb │line6-bbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb
+line7-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb │line7-bbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb
+line8-bbbbbbbbbbbbbbbbbbbbbbbb│line18-bbb│line8-bbbbbbbbbbbbbbbbbbbbbbb │line8-bbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb
+line9-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb │line9-bbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb
+line10-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb │line10-bbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+line11-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb │line11-bbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb │line12-bbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb │line13-bbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbb│line19-bbb│line14-bbbbbbbbbbbbbbbbbbbbbb │line14-bbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb │line15-bbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb │line16-bbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb │line17-bbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb │line18-bbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb │line19-bbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ │prompt $ █ │prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_with_current_pane.snap
new file mode 100644
index 00000000..a6002f87
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_left_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+────────────────────────────────────────────────────────────┤line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│prompt $
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb├────────────────────────────────────────────────────────────
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_right_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_right_with_current_pane.snap
new file mode 100644
index 00000000..7a460b4e
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_aligned_right_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $
+a ├────────────────────────────────────────────────────────────
+prompt $ │line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+────────────────────────────────────────────────────────────┤line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_and_below.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_and_below.snap
new file mode 100644
index 00000000..0d404ed1
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_above_and_below.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_and_right_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_and_right_with_current_pane.snap
new file mode 100644
index 00000000..e86d3efe
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_and_right_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line19-bbbbbbbbbbbbbbbbbbbbbbb│prompt $
+────────────────────────────────────────────────────────────┤bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb├─────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│prompt $ █ │line12-bbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb├──────────────────────────────┤line13-bbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_right.snap
new file mode 100644
index 00000000..14d9e507
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_right.snap
@@ -0,0 +1,45 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line1-bbbbbbbbbbbbbbbbbbbbbbb │line1-bbbbbbbb│line1-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line2-bbbbbbbbbbbbbbbbbbbbbbb │line2-bbbbbbbb│line2-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line3-bbbbbbbbbbbbbbbbbbbbbbb │line3-bbbbbbbb│line3-bbbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbb│line4-bbbbbbbbbbbbbbbbbbbbbbb │line4-bbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb │line5-bbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb │line6-bbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb │line7-bbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb │line8-bbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb │line9-bbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaa│line18-bbb│line10-bbbbbbbbbbbbbbbbbbbbbb │line10-bbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb │line11-bbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb │line12-bbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb │line13-bbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb │line14-bbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb │line15-bbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbb│line16-bbbbbbbbbbbbbbbbbbbbbb │line16-bbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb │line17-bbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb │line18-bbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb │line19-bbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │bbbbbbbbbb│prompt $ █ │prompt $ │prompt $
+──────────────────────────────┤bbbbbbbbbb│ │ ├─────────────────────────────
+line2-bbbbbbbbbbbbbbbbbbbbbbbb│prompt $ │ │ │line2-bbbbbbbbbbbbbbbbbbbbbbb
+line3-bbbbbbbbbbbbbbbbbbbbbbbb├──────────┴──────────────────────────────────┴──────────────┤line3-bbbbbbbbbbbbbbbbbbbbbbb
+line4-bbbbbbbbbbbbbbbbbbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb
+line5-bbbbbbbbbbbbbbbbbbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb
+line6-bbbbbbbbbbbbbbbbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb
+line7-bbbbbbbbbbbbbbbbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb
+line8-bbbbbbbbbbbbbbbbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb
+line9-bbbbbbbbbbbbbbbbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb
+line10-bbbbbbbbbbbbbbbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+line11-bbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_with_current_pane.snap
new file mode 100644
index 00000000..8e8a9688
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_left_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+────────────────────────────────────────────────────────────┤line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│prompt $ █
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb├────────────────────────────────────────────────────────────
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_right_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_right_with_current_pane.snap
new file mode 100644
index 00000000..1a90fef7
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_down__resize_down_with_panes_below_aligned_right_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_down.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $
+a ├────────────────────────────────────────────────────────────
+prompt $ █ │line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+────────────────────────────────────────────────────────────┤line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__cannot_resize_left_when_pane_to_the_left_is_at_minimum_width.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__cannot_resize_left_when_pane_to_the_left_is_at_minimum_width.snap
new file mode 100644
index 00000000..214bc8c3
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__cannot_resize_left_when_pane_to_the_left_is_at_minimum_width.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+14-b│lin3
+b │lin4
+line│lin5
+15-b│lin6
+b │lin7
+line│lin8
+16-b│lin9
+b │ln10
+line│ln11
+17-b│ln12
+b │ln13
+line│ln14
+18-b│ln15
+b │ln16
+line│ln17
+19-b│ln18
+b │ln19
+prom│prom
+pt $│pt $
+ │ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_multiple_panes_to_the_left.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_multiple_panes_to_the_left.snap
new file mode 100644
index 00000000..7cc426f7
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_multiple_panes_to_the_left.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+──────────────────────────────────────────────────┤line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_pane_to_the_left.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_pane_to_the_left.snap
new file mode 100644
index 00000000..7dc8c588
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_pane_to_the_left.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaa │line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_pane_to_the_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_pane_to_the_right.snap
new file mode 100644
index 00000000..16597014
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_pane_to_the_right.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaa │line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane.snap
new file mode 100644
index 00000000..fbf57dd7
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
+──────────────────────────────────────────────────┴─────────┬────────────────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pane.snap
new file mode 100644
index 00000000..6eb2cd18
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+──────────────────────────────────────────────────┬─────────┴────────────────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
+──────────────────────────────────────────────────┴─────────┬────────────────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_above_and_below.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_above_and_below.snap
new file mode 100644
index 00000000..02516734
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_above_and_below.snap
@@ -0,0 +1,45 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+a │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+──────────────────────────────────────────────────┬─────────┴────────────────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa├──────────────────────────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaa │line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │prompt $ █
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa├──────────────────────────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+──────────────────────────────────────────────────┴─────────┬────────────────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_with_current_pane.snap
new file mode 100644
index 00000000..58edf926
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_aligned_top_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+──────────────────────────────────────────────────┬─────────┴────────────────────────────────────────────────────────────
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_and_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_and_right.snap
new file mode 100644
index 00000000..4431134f
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_left_and_right.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbbbbbbbbbbbb │line1-bbbbbbbbbbbbbbbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line2-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │line3-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │line5-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbbbbbbbbbbbb │line7-bbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │line9-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbbbbbbbbbbbb │line13-bbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │line15-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbbbbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane.snap
new file mode 100644
index 00000000..549b15e5
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │prompt $
+──────────────────────────────────────────────────┴─────────┬────────────────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_pane.snap
new file mode 100644
index 00000000..bdee0bd7
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+──────────────────────────────────────────────────┬─────────┴────────────────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │prompt $
+──────────────────────────────────────────────────┴─────────┬────────────────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_above_and_below.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_above_and_below.snap
new file mode 100644
index 00000000..62a92718
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_above_and_below.snap
@@ -0,0 +1,45 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+a │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+──────────────────────────────────────────────────┬─────────┴────────────────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaa │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+──────────────────────────────────────────────────┤line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+──────────────────────────────────────────────────┤line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+──────────────────────────────────────────────────┴─────────┬────────────────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_with_current_pane.snap
new file mode 100644
index 00000000..654b16d0
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_left__resize_left_with_panes_to_the_right_aligned_top_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_left.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+──────────────────────────────────────────────────┬─────────┴────────────────────────────────────────────────────────────
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__cannot_resize_right_when_pane_to_the_left_is_at_minimum_width.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__cannot_resize_right_when_pane_to_the_left_is_at_minimum_width.snap
new file mode 100644
index 00000000..9d366dad
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__cannot_resize_right_when_pane_to_the_left_is_at_minimum_width.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+14-b│lin3
+b │lin4
+line│lin5
+15-b│lin6
+b │lin7
+line│lin8
+16-b│lin9
+b │ln10
+line│ln11
+17-b│ln12
+b │ln13
+line│ln14
+18-b│ln15
+b │ln16
+line│ln17
+19-b│ln18
+b │ln19
+prom│prom
+pt $│pt $
+ │ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_multiple_panes_to_the_left.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_multiple_panes_to_the_left.snap
new file mode 100644
index 00000000..56b58e28
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_multiple_panes_to_the_left.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+prompt $ │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+──────────────────────────────────────────────────────────────────────┤bbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_pane_to_the_left.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_pane_to_the_left.snap
new file mode 100644
index 00000000..2e3ea9f7
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_pane_to_the_left.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_pane_to_the_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_pane_to_the_right.snap
new file mode 100644
index 00000000..87eb82c7
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_pane_to_the_right.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane.snap
new file mode 100644
index 00000000..9fafc30a
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+prompt $ │prompt $ █
+────────────────────────────────────────────────────────────┬─────────┴──────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_pane.snap
new file mode 100644
index 00000000..21cbb652
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┴─────────┬──────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+prompt $ │prompt $ █
+────────────────────────────────────────────────────────────┬─────────┴──────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_above_and_below.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_above_and_below.snap
new file mode 100644
index 00000000..87714cef
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_above_and_below.snap
@@ -0,0 +1,45 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+a │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┴─────────┬──────────────────────────────────────────────────
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ├──────────────────────────────────────────────────
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $ █
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ├──────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┬─────────┴──────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_with_current_pane.snap
new file mode 100644
index 00000000..1113bb73
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_aligned_top_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┴─────────┬──────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_and_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_and_right.snap
new file mode 100644
index 00000000..fa310617
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_left_and_right.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+a │bbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+a │line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+a │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+a │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+prompt $ │prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane.snap
new file mode 100644
index 00000000..7bb38a1a
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+prompt $ █ │prompt $
+────────────────────────────────────────────────────────────┬─────────┴──────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_pane.snap
new file mode 100644
index 00000000..3c65a787
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┴─────────┬──────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+prompt $ █ │prompt $
+────────────────────────────────────────────────────────────┬─────────┴──────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_above_and_below.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_above_and_below.snap
new file mode 100644
index 00000000..2c2a64e0
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_above_and_below.snap
@@ -0,0 +1,45 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+a │line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┴─────────┬──────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │bbbbbbbbbb
+prompt $ │line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+──────────────────────────────────────────────────────────────────────┤bbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+prompt $ █ │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+──────────────────────────────────────────────────────────────────────┤bbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┬─────────┴──────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_with_current_pane.snap
new file mode 100644
index 00000000..24411c3e
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_right__resize_right_with_panes_to_the_right_aligned_top_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_right.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┴─────────┬──────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │bbbbbbbbbb
+prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__cannot_resize_up_when_pane_above_is_at_minimum_height.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__cannot_resize_up_when_pane_above_is_at_minimum_height.snap
new file mode 100644
index 00000000..e1f6bee0
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__cannot_resize_up_when_pane_above_is_at_minimum_height.snap
@@ -0,0 +1,10 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_multiple_panes_above.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_multiple_panes_above.snap
new file mode 100644
index 00000000..cf184ecf
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_multiple_panes_above.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
+────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_pane_above.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_pane_above.snap
new file mode 100644
index 00000000..877beb41
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_pane_above.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_pane_below.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_pane_below.snap
new file mode 100644
index 00000000..676082c1
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_pane_below.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_and_right_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_and_right_with_current_pane.snap
new file mode 100644
index 00000000..24d805db
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_and_right_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $ │line18-bbbbbbbbbbbbbbbbbbbbbb
+a ├──────────────────────────────┤line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line10-bbbbbbbbbbbbbbbbbbbbbbb│prompt $
+────────────────────────────────────────────────────────────┤line11-bbbbbbbbbbbbbbbbbbbbbbb├─────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_and_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_and_right.snap
new file mode 100644
index 00000000..aa571254
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_and_right.snap
@@ -0,0 +1,45 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line1-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line2-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line3-bbbbbbbbbbbbbbbbbbbbbbb
+a │line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaa│line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb
+a │line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $ │line18-bbbbbbbbbbbbbbbbbbbbbb
+a ├──────────┬──────────────────────────────────┬──────────────┤line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │bbbbbbbbbb│line1-bbbbbbbbbbbbbbbbbbbbbbb │line1-bbbbbbbb│prompt $
+──────────────────────────────┤bbbbbbbbbb│line2-bbbbbbbbbbbbbbbbbbbbbbb │line2-bbbbbbbb├─────────────────────────────
+line2-bbbbbbbbbbbbbbbbbbbbbbbb│line17-bbb│line3-bbbbbbbbbbbbbbbbbbbbbbb │line3-bbbbbbbb│line2-bbbbbbbbbbbbbbbbbbbbbbb
+line3-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb │line4-bbbbbbbb│line3-bbbbbbbbbbbbbbbbbbbbbbb
+line4-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb │line5-bbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb
+line5-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb │line6-bbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb
+line6-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb │line7-bbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb
+line7-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb │line8-bbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb
+line8-bbbbbbbbbbbbbbbbbbbbbbbb│line18-bbb│line9-bbbbbbbbbbbbbbbbbbbbbbb │line9-bbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb
+line9-bbbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb │line10-bbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb
+line10-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb │line11-bbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+line11-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb │line12-bbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb │line13-bbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb │line14-bbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbb│line19-bbb│line15-bbbbbbbbbbbbbbbbbbbbbb │line15-bbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb │line16-bbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb │line17-bbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb │line18-bbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb │line19-bbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbb│bbbbbbbbbb│prompt $ █ │prompt $ │line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ │ │ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_with_current_pane.snap
new file mode 100644
index 00000000..463b225f
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_left_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $
+a ├────────────────────────────────────────────────────────────
+prompt $ │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+────────────────────────────────────────────────────────────┤line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_right_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_right_with_current_pane.snap
new file mode 100644
index 00000000..dfe97594
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_aligned_right_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+a │line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+────────────────────────────────────────────────────────────┤line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│prompt $
+line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb├────────────────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_and_below.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_and_below.snap
new file mode 100644
index 00000000..471fe924
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_above_and_below.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_and_right_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_and_right_with_current_pane.snap
new file mode 100644
index 00000000..d2ba58b3
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_and_right_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $ █ │line18-bbbbbbbbbbbbbbbbbbbbbb
+a ├──────────────────────────────┤line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line10-bbbbbbbbbbbbbbbbbbbbbbb│prompt $
+────────────────────────────────────────────────────────────┤line11-bbbbbbbbbbbbbbbbbbbbbbb├─────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_right.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_right.snap
new file mode 100644
index 00000000..dfa9283b
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_right.snap
@@ -0,0 +1,45 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line3-bbbbbbbbbbbbbbbbbbbbbbb │line3-bbbbbbbb│line1-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb │line4-bbbbbbbb│line2-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb │line5-bbbbbbbb│line3-bbbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb │line6-bbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb
+line17-aaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb │line7-bbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbb│line8-bbbbbbbbbbbbbbbbbbbbbbb │line8-bbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb │line9-bbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb │line10-bbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb │line11-bbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb │line12-bbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb │line13-bbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbb│line14-bbbbbbbbbbbbbbbbbbbbbb │line14-bbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb │line15-bbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+a │bbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb │line16-bbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb │line17-bbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb │line18-bbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│bbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbb │line19-bbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $ │prompt $ █ │prompt $ │line18-bbbbbbbbbbbbbbbbbbbbbb
+a ├──────────┴──────────────────────────────────┴──────────────┤line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│prompt $
+──────────────────────────────┤line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb├─────────────────────────────
+line2-bbbbbbbbbbbbbbbbbbbbbbbb│line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line2-bbbbbbbbbbbbbbbbbbbbbbb
+line3-bbbbbbbbbbbbbbbbbbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line3-bbbbbbbbbbbbbbbbbbbbbbb
+line4-bbbbbbbbbbbbbbbbbbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line4-bbbbbbbbbbbbbbbbbbbbbbb
+line5-bbbbbbbbbbbbbbbbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line5-bbbbbbbbbbbbbbbbbbbbbbb
+line6-bbbbbbbbbbbbbbbbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbb
+line7-bbbbbbbbbbbbbbbbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbb
+line8-bbbbbbbbbbbbbbbbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbb
+line9-bbbbbbbbbbbbbbbbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbb
+line10-bbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbb
+line11-bbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbb│prompt $ │line19-bbbbbbbbbbbbbbbbbbbbbb
+prompt $ │ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_with_current_pane.snap
new file mode 100644
index 00000000..3e41290c
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_left_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│prompt $ █
+a ├────────────────────────────────────────────────────────────
+prompt $ │line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+────────────────────────────────────────────────────────────┤line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_right_with_current_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_right_with_current_pane.snap
new file mode 100644
index 00000000..fb31ae4b
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__resize_up__resize_up_with_panes_below_aligned_right_with_current_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/resize_up.rs
+expression: snapshot_before_quit
+
+---
+a │line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █ │line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+────────────────────────────────────────────────────────────┤line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│prompt $
+line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb├────────────────────────────────────────────────────────────
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_last_pane_in_a_tab.snap b/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_last_pane_in_a_tab.snap
new file mode 100644
index 00000000..5196e5ca
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_last_pane_in_a_tab.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/tabs.rs
+expression: snapshot_before_quit
+
+---
+line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_tab.snap b/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_tab.snap
new file mode 100644
index 00000000..24ba5565
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_tab.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/tabs.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_the_middle_tab.snap b/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_the_middle_tab.snap
new file mode 100644
index 00000000..5196e5ca
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_the_middle_tab.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/tabs.rs
+expression: snapshot_before_quit
+
+---
+line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_the_tab_that_has_a_pane_in_fullscreen.snap b/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_the_tab_that_has_a_pane_in_fullscreen.snap
new file mode 100644
index 00000000..24ba5565
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__tabs__close_the_tab_that_has_a_pane_in_fullscreen.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/tabs.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__tabs__closing_last_tab_exits_the_app.snap b/src/tests/integration/snapshots/zellij__tests__integration__tabs__closing_last_tab_exits_the_app.snap
new file mode 100644
index 00000000..24ba5565
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__tabs__closing_last_tab_exits_the_app.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/tabs.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__tabs__open_new_tab.snap b/src/tests/integration/snapshots/zellij__tests__integration__tabs__open_new_tab.snap
new file mode 100644
index 00000000..5196e5ca
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__tabs__open_new_tab.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/tabs.rs
+expression: snapshot_before_quit
+
+---
+line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__tabs__switch_to_next_tab.snap b/src/tests/integration/snapshots/zellij__tests__integration__tabs__switch_to_next_tab.snap
new file mode 100644
index 00000000..24ba5565
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__tabs__switch_to_next_tab.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/tabs.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__tabs__switch_to_prev_tab.snap b/src/tests/integration/snapshots/zellij__tests__integration__tabs__switch_to_prev_tab.snap
new file mode 100644
index 00000000..24ba5565
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__tabs__switch_to_prev_tab.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/tabs.rs
+expression: snapshot_before_quit
+
+---
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__toggle_fullscreen__adding_new_terminal_in_fullscreen.snap b/src/tests/integration/snapshots/zellij__tests__integration__toggle_fullscreen__adding_new_terminal_in_fullscreen.snap
new file mode 100644
index 00000000..74cf9c82
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__toggle_fullscreen__adding_new_terminal_in_fullscreen.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/toggle_fullscreen.rs
+expression: snapshot_before_quit
+
+---
+a │line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │prompt $
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa├────────────────────────────────────────────────────────────
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+a │line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ │prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__toggle_fullscreen__move_focus_is_disabled_in_fullscreen.snap b/src/tests/integration/snapshots/zellij__tests__integration__toggle_fullscreen__move_focus_is_disabled_in_fullscreen.snap
new file mode 100644
index 00000000..be8af70c
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__toggle_fullscreen__move_focus_is_disabled_in_fullscreen.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/toggle_fullscreen.rs
+expression: snapshot_before_quit
+
+---
+line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+prompt $ █
diff --git a/src/tests/integration/tabs.rs b/src/tests/integration/tabs.rs
new file mode 100644
index 00000000..a4a0f52a
--- /dev/null
+++ b/src/tests/integration/tabs.rs
@@ -0,0 +1,322 @@
+use insta::assert_snapshot;
+
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{panes::PositionAndSize, tests::utils::commands::CLOSE_PANE_IN_PANE_MODE};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ CLOSE_TAB_IN_TAB_MODE, COMMAND_TOGGLE, ESC, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT,
+ SPLIT_DOWN_IN_PANE_MODE, SWITCH_NEXT_TAB_IN_TAB_MODE, SWITCH_PREV_TAB_IN_TAB_MODE, TAB_MODE,
+ TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(*fake_win_size)
+}
+
+#[test]
+pub fn open_new_tab() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &NEW_TAB_IN_TAB_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn switch_to_prev_tab() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &NEW_TAB_IN_TAB_MODE,
+ &SWITCH_PREV_TAB_IN_TAB_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn switch_to_next_tab() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &NEW_TAB_IN_TAB_MODE,
+ &SWITCH_NEXT_TAB_IN_TAB_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_tab() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &NEW_TAB_IN_TAB_MODE,
+ &CLOSE_TAB_IN_TAB_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_last_pane_in_a_tab() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &NEW_TAB_IN_TAB_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &CLOSE_PANE_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_the_middle_tab() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &NEW_TAB_IN_TAB_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &NEW_TAB_IN_TAB_MODE,
+ &SWITCH_PREV_TAB_IN_TAB_MODE,
+ &CLOSE_TAB_IN_TAB_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn close_the_tab_that_has_a_pane_in_fullscreen() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &NEW_TAB_IN_TAB_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &NEW_TAB_IN_TAB_MODE,
+ &SWITCH_PREV_TAB_IN_TAB_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &CLOSE_TAB_IN_TAB_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn closing_last_tab_exits_the_app() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &ESC,
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &TAB_MODE,
+ &NEW_TAB_IN_TAB_MODE,
+ &CLOSE_TAB_IN_TAB_MODE,
+ &CLOSE_TAB_IN_TAB_MODE,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/integration/toggle_fullscreen.rs b/src/tests/integration/toggle_fullscreen.rs
new file mode 100644
index 00000000..943e91a5
--- /dev/null
+++ b/src/tests/integration/toggle_fullscreen.rs
@@ -0,0 +1,77 @@
+use insta::assert_snapshot;
+
+use crate::panes::PositionAndSize;
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+use crate::tests::utils::commands::{
+ COMMAND_TOGGLE, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE,
+ SPLIT_RIGHT_IN_PANE_MODE, TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
+};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(*fake_win_size)
+}
+
+#[test]
+pub fn adding_new_terminal_in_fullscreen() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
+ &SPLIT_DOWN_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn move_focus_is_disabled_in_fullscreen() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[
+ &COMMAND_TOGGLE,
+ &COMMAND_TOGGLE,
+ &PANE_MODE,
+ &SPLIT_RIGHT_IN_PANE_MODE,
+ &TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
+ &MOVE_FOCUS_IN_PANE_MODE,
+ &QUIT,
+ ]);
+ start(Box::new(fake_input_output.clone()), CliArgs::default());
+
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
new file mode 100644
index 00000000..6e07c96d
--- /dev/null
+++ b/src/tests/mod.rs
@@ -0,0 +1,5 @@
+pub mod fakes;
+pub mod integration;
+pub mod possible_tty_inputs;
+pub mod tty_inputs;
+pub mod utils;
diff --git a/src/tests/possible_tty_inputs.rs b/src/tests/possible_tty_inputs.rs
new file mode 100644
index 00000000..fb1cc5c6
--- /dev/null
+++ b/src/tests/possible_tty_inputs.rs
@@ -0,0 +1,94 @@
+use crate::tests::tty_inputs::{
+ COL_10, COL_121, COL_14, COL_15, COL_19, COL_20, COL_24, COL_29, COL_30, COL_34, COL_39, COL_4,
+ COL_40, COL_50, COL_60, COL_70, COL_8, COL_9, COL_90, COL_96,
+};
+use std::collections::HashMap;
+use std::fs;
+use std::path::PathBuf;
+
+#[derive(Clone, Debug)]
+pub struct Bytes {
+ pub content: Vec<u8>,
+ pub read_position: usize,
+}
+
+impl Bytes {
+ pub fn new() -> Self {
+ Bytes {
+ content: vec![],
+ read_position: 0,
+ }
+ }
+ pub fn from_file_in_fixtures(file_name: &str) -> Self {
+ let mut path_to_file = PathBuf::new();
+ path_to_file.push("src");
+ path_to_file.push("tests");
+ path_to_file.push("fixtures");
+ path_to_file.push(file_name);
+ let content = fs::read(path_to_file)
+ .unwrap_or_else(|_| panic!("could not read fixture {:?}", &file_name));
+ Bytes {
+ content,
+ read_position: 0,
+ }
+ }
+ pub fn content_from_str(mut self, content: &[&'static str]) -> Self {
+ let mut content_as_bytes = vec![];
+ for line in content {
+ for char in line.chars() {
+ content_as_bytes.push(char as u8);
+ }
+ }
+ self.content = content_as_bytes;
+ self
+ }
+ pub fn set_read_position(&mut self, read_position: usize) {
+ self.read_position = read_position;
+ }
+}
+
+pub fn get_possible_tty_inputs() -> HashMap<u16, Bytes> {
+ // the key is the column count for this terminal input
+ let mut possible_inputs = HashMap::new();
+ let col_4_bytes = Bytes::new().content_from_str(&COL_4);
+ let col_8_bytes = Bytes::new().content_from_str(&COL_8);
+ let col_9_bytes = Bytes::new().content_from_str(&COL_9);
+ let col_10_bytes = Bytes::new().content_from_str(&COL_10);
+ let col_14_bytes = Bytes::new().content_from_str(&COL_14);
+ let col_15_bytes = Bytes::new().content_from_str(&COL_15);
+ let col_19_bytes = Bytes::new().content_from_str(&COL_19);
+ let col_20_bytes = Bytes::new().content_from_str(&COL_20);
+ let col_24_bytes = Bytes::new().content_from_str(&COL_24);
+ let col_29_bytes = Bytes::new().content_from_str(&COL_29);
+ let col_30_bytes = Bytes::new().content_from_str(&COL_30);
+ let col_34_bytes = Bytes::new().content_from_str(&COL_34);
+ let col_39_bytes = Bytes::new().content_from_str(&COL_39);
+ let col_40_bytes = Bytes::new().content_from_str(&COL_40);
+ let col_50_bytes = Bytes::new().content_from_str(&COL_50);
+ let col_60_bytes = Bytes::new().content_from_str(&COL_60);
+ let col_70_bytes = Bytes::new().content_from_str(&COL_70);
+ let col_90_bytes = Bytes::new().content_from_str(&COL_90);
+ let col_96_bytes = Bytes::new().content_from_str(&COL_96);
+ let col_121_bytes = Bytes::new().content_from_str(&COL_121);
+ possible_inputs.insert(4, col_4_bytes);
+ possible_inputs.insert(8, col_8_bytes);
+ possible_inputs.insert(9, col_9_bytes);
+ possible_inputs.insert(10, col_10_bytes);
+ possible_inputs.insert(14, col_14_bytes);
+ possible_inputs.insert(15, col_15_bytes);
+ possible_inputs.insert(19, col_19_bytes);
+ possible_inputs.insert(20, col_20_bytes);
+ possible_inputs.insert(24, col_24_bytes);
+ possible_inputs.insert(29, col_29_bytes);
+ possible_inputs.insert(30, col_30_bytes);
+ possible_inputs.insert(34, col_34_bytes);
+ possible_inputs.insert(39, col_39_bytes);
+ possible_inputs.insert(40, col_40_bytes);
+ possible_inputs.insert(50, col_50_bytes);
+ possible_inputs.insert(60, col_60_bytes);
+ possible_inputs.insert(70, col_70_bytes);
+ possible_inputs.insert(90, col_90_bytes);
+ possible_inputs.insert(96, col_96_bytes);
+ possible_inputs.insert(121, col_121_bytes);
+ possible_inputs
+}
diff --git a/src/tests/tty_inputs.rs b/src/tests/tty_inputs.rs
new file mode 100644
index 00000000..09f67abe
--- /dev/null
+++ b/src/tests/tty_inputs.rs
@@ -0,0 +1,458 @@
+pub const COL_121: [&str; 20] = [
+ "line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "prompt $ ",
+];
+
+pub const COL_4: [&str; 20] = [
+ "lin1\r\n",
+ "lin2\r\n",
+ "lin3\r\n",
+ "lin4\r\n",
+ "lin5\r\n",
+ "lin6\r\n",
+ "lin7\r\n",
+ "lin8\r\n",
+ "lin9\r\n",
+ "ln10\r\n",
+ "ln11\r\n",
+ "ln12\r\n",
+ "ln13\r\n",
+ "ln14\r\n",
+ "ln15\r\n",
+ "ln16\r\n",
+ "ln17\r\n",
+ "ln18\r\n",
+ "ln19\r\n",
+ "prompt $ ",
+];
+
+pub const COL_8: [&str; 20] = [
+ "line1-bb\r\n",
+ "line2-bb\r\n",
+ "line3-bb\r\n",
+ "line4-bb\r\n",
+ "line5-bb\r\n",
+ "line6-bb\r\n",
+ "line7-bb\r\n",
+ "line8-bb\r\n",
+ "line9-bb\r\n",
+ "line10-b\r\n",
+ "line11-b\r\n",
+ "line12-b\r\n",
+ "line13-b\r\n",
+ "line14-b\r\n",
+ "line15-b\r\n",
+ "line16-b\r\n",
+ "line17-b\r\n",
+ "line18-b\r\n",
+ "line19-b\r\n",
+ "prompt $ ",
+];
+
+pub const COL_9: [&str; 20] = [
+ "line1-bbb\r\n",
+ "line2-bbb\r\n",
+ "line3-bbb\r\n",
+ "line4-bbb\r\n",
+ "line5-bbb\r\n",
+ "line6-bbb\r\n",
+ "line7-bbb\r\n",
+ "line8-bbb\r\n",
+ "line9-bbb\r\n",
+ "line10-bb\r\n",
+ "line11-bb\r\n",
+ "line12-bb\r\n",
+ "line13-bb\r\n",
+ "line14-bb\r\n",
+ "line15-bb\r\n",
+ "line16-bb\r\n",
+ "line17-bb\r\n",
+ "line18-bb\r\n",
+ "line19-bb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_10: [&str; 20] = [
+ "line1-bbbb\r\n",
+ "line2-bbbb\r\n",
+ "line3-bbbb\r\n",
+ "line4-bbbb\r\n",
+ "line5-bbbb\r\n",
+ "line6-bbbb\r\n",
+ "line7-bbbb\r\n",
+ "line8-bbbb\r\n",
+ "line9-bbbb\r\n",
+ "line10-bbb\r\n",
+ "line11-bbb\r\n",
+ "line12-bbb\r\n",
+ "line13-bbb\r\n",
+ "line14-bbb\r\n",
+ "line15-bbb\r\n",
+ "line16-bbb\r\n",
+ "line17-bbb\r\n",
+ "line18-bbb\r\n",
+ "line19-bbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_14: [&str; 20] = [
+ "line1-bbbbbbbb\r\n",
+ "line2-bbbbbbbb\r\n",
+ "line3-bbbbbbbb\r\n",
+ "line4-bbbbbbbb\r\n",
+ "line5-bbbbbbbb\r\n",
+ "line6-bbbbbbbb\r\n",
+ "line7-bbbbbbbb\r\n",
+ "line8-bbbbbbbb\r\n",
+ "line9-bbbbbbbb\r\n",
+ "line10-bbbbbbb\r\n",
+ "line11-bbbbbbb\r\n",
+ "line12-bbbbbbb\r\n",
+ "line13-bbbbbbb\r\n",
+ "line14-bbbbbbb\r\n",
+ "line15-bbbbbbb\r\n",
+ "line16-bbbbbbb\r\n",
+ "line17-bbbbbbb\r\n",
+ "line18-bbbbbbb\r\n",
+ "line19-bbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_15: [&str; 20] = [
+ "line1-bbbbbbbbb\r\n",
+ "line2-bbbbbbbbb\r\n",
+ "line3-bbbbbbbbb\r\n",
+ "line4-bbbbbbbbb\r\n",
+ "line5-bbbbbbbbb\r\n",
+ "line6-bbbbbbbbb\r\n",
+ "line7-bbbbbbbbb\r\n",
+ "line8-bbbbbbbbb\r\n",
+ "line9-bbbbbbbbb\r\n",
+ "line10-bbbbbbbb\r\n",
+ "line11-bbbbbbbb\r\n",
+ "line12-bbbbbbbb\r\n",
+ "line13-bbbbbbbb\r\n",
+ "line14-bbbbbbbb\r\n",
+ "line15-bbbbbbbb\r\n",
+ "line16-bbbbbbbb\r\n",
+ "line17-bbbbbbbb\r\n",
+ "line18-bbbbbbbb\r\n",
+ "line19-bbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_19: [&str; 20] = [
+ "line1-bbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_20: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_24: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+pub const COL_29: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_30: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_34: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_40: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_39: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_50: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_60: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_70: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_90: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
+pub const COL_96: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
diff --git a/src/tests/utils.rs b/src/tests/utils.rs
new file mode 100644
index 00000000..587d36fd
--- /dev/null
+++ b/src/tests/utils.rs
@@ -0,0 +1,79 @@
+use crate::panes::PositionAndSize;
+use crate::panes::TerminalPane;
+
+pub fn get_output_frame_snapshots(
+ output_frames: &[Vec<u8>],
+ win_size: &PositionAndSize,
+) -> Vec<String> {
+ let mut vte_parser = vte::Parser::new();
+ let main_pid = 0;
+ let mut terminal_output = TerminalPane::new(main_pid, *win_size);
+
+ let mut snapshots = vec![];
+ for frame in output_frames.iter() {
+ for byte in frame.iter() {
+ vte_parser.advance(&mut terminal_output, *byte);
+ }
+ let output_lines = terminal_output.read_buffer_as_lines();
+ let cursor_coordinates = terminal_output.cursor_coordinates();
+ let mut snapshot = String::new();
+ for (line_index, line) in output_lines.iter().enumerate() {
+ for (character_index, terminal_character) in line.iter().enumerate() {
+ if let Some((cursor_x, cursor_y)) = cursor_coordinates {
+ if line_index == cursor_y && character_index == cursor_x {
+ snapshot.push('█');
+ continue;
+ }
+ }
+ snapshot.push(terminal_character.character);
+ }
+ if line_index != output_lines.len() - 1 {
+ snapshot.push('\n');
+ }
+ }
+ snapshots.push(snapshot);
+ }
+ snapshots
+}
+
+pub fn get_next_to_last_snapshot(mut snapshots: Vec<String>) -> Option<String> {
+ if snapshots.len() < 2 {
+ None
+ } else {
+ Some(snapshots.remove(snapshots.len() - 2))
+ }
+}
+
+pub mod commands {
+ pub const COMMAND_TOGGLE: [u8; 1] = [7]; // ctrl-g
+ pub const QUIT: [u8; 1] = [113]; // q
+ pub const ESC: [u8; 1] = [27];
+
+ pub const PANE_MODE: [u8; 1] = [112]; // p
+ pub const SPAWN_TERMINAL_IN_PANE_MODE: [u8; 1] = [110]; // n
+ pub const MOVE_FOCUS_IN_PANE_MODE: [u8; 1] = [112]; // p
+ pub const SPLIT_DOWN_IN_PANE_MODE: [u8; 1] = [100]; // d
+ pub const SPLIT_RIGHT_IN_PANE_MODE: [u8; 1] = [114]; // r
+ pub const TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE: [u8; 1] = [102]; // f
+ pub const CLOSE_PANE_IN_PANE_MODE: [u8; 1] = [120]; // x
+ pub const MOVE_FOCUS_DOWN_IN_PANE_MODE: [u8; 1] = [106]; // j
+ pub const MOVE_FOCUS_UP_IN_PANE_MODE: [u8; 1] = [107]; // k
+ pub const MOVE_FOCUS_LEFT_IN_PANE_MODE: [u8; 1] = [104]; // h
+ pub const MOVE_FOCUS_RIGHT_IN_PANE_MODE: [u8; 1] = [108]; // l
+
+ pub const SCROLL_MODE: [u8; 1] = [115]; // s
+ pub const SCROLL_UP_IN_SCROLL_MODE: [u8; 1] = [107]; // k
+ pub const SCROLL_DOWN_IN_SCROLL_MODE: [u8; 1] = [106]; // j
+
+ pub const RESIZE_MODE: [u8; 1] = [114]; // r
+ pub const RESIZE_DOWN_IN_RESIZE_MODE: [u8; 1] = [106]; // j
+ pub const RESIZE_UP_IN_RESIZE_MODE: [u8; 1] = [107]; // k
+ pub const RESIZE_LEFT_IN_RESIZE_MODE: [u8; 1] = [104]; // h
+ pub const RESIZE_RIGHT_IN_RESIZE_MODE: [u8; 1] = [108]; // l
+
+ pub const TAB_MODE: [u8; 1] = [116]; // t
+ pub const NEW_TAB_IN_TAB_MODE: [u8; 1] = [110]; // n
+ pub const SWITCH_NEXT_TAB_IN_TAB_MODE: [u8; 1] = [108]; // l
+ pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h
+ pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x
+}