feat(ui): swap layouts and stacked panes (#2167)

* relayout working with hard coded layout

* work

* refactor(layout): PaneLayout => TiledPaneLayout

* tests passing

* tests passing

* tests passing

* stacked panes and passing tests

* tests for stacked panes

* refactor(panes): stacked panes

* fix: focusing into stacked panes from the left/right

* fix(layouts): handle stacked layouts in the middle of the screen

* fix(pane-stack): focus correctly when coming to stack from above/below

* fix(stacked-panes): resize stack

* fix(stacked-panes): focus with mouse

* fix(stacked-panes): focus next pane

* fix(layout-applier): sane focus order

* fix(stacked-panes): better titles for one-liners

* fix(stacked-panes): handle moving pane location in stack

* fix(relayout): properly calculate display area

* fix(relayout): properly calculate rounding errors

* fix(stacked-panes): properly handle closing a pane near a stack

* fix(swap-layouts): adjust swap layout sort order

* feat(swap-layouts): ui + ux

* fix(swap-layouts): include base layout

* refactor(layout): remove unused method

* fix(swap-layouts): respect pane contents and focus

* work

* fix(swap-layouts): load swap layouts from external file

* fix(swap-layouts): properly truncate layout children

* fix(stacked-panes): allow stacked panes to become fullscreen

* fix(swap-layouts): work with multiple tabs

* fix(swap-layouts): embed/eject panes properly with auto-layout

* fix(stacked-panes): close last pane in stack

* fix(stacked-panes): move focus for all clients in stack

* fix(floating-panes): set layout damaged when moving panes

* fix(relayout): move out of unfitting layout when resizing whole tab

* fix(ui): background color for swap layout indicator

* fix(keybinds): add switch next layout in tmux

* fix(ui): swap layout indication in compact layout

* fix(compact): correct swap constraint

* fix(tests): tmux swap config shortcut

* fix(resizes): cache resizes so as not to confuse panes (eg. vim) with multiple resizes that it debounces weirdly

* feat(cli): dump swap layouts

* fix(ui): stacked panes without pane frames

* fix(ux): move pane forward/backwards also with floating panes

* refactor(lint): remove unused stuff

* refactor(tab): move swap layouts to separate file

* style(fmt): rustfmt

* style(fmt): rustfmt

* refactor(panes): various cleanups

* chore(deps): upgrade termwiz to get alt left-bracket

* fix(assets): merge conflicts of binary files

* style(fmt): rustfmt

* style(clippy): no thank you!

* chore(repo): remove garbage file
This commit is contained in:
Aram Drevekenin 2023-02-17 12:05:50 +01:00 committed by GitHub
parent 1517036c24
commit f1ff272b0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
204 changed files with 16278 additions and 2904 deletions

View file

@ -256,6 +256,8 @@ pub fn tab_line(
palette: Palette, palette: Palette,
capabilities: PluginCapabilities, capabilities: PluginCapabilities,
mode: InputMode, mode: InputMode,
active_swap_layout_name: &Option<String>,
is_swap_layout_dirty: bool,
) -> Vec<LinePart> { ) -> Vec<LinePart> {
let mut tabs_after_active = all_tabs.split_off(active_tab_index); let mut tabs_after_active = all_tabs.split_off(active_tab_index);
let mut tabs_before_active = all_tabs; let mut tabs_before_active = all_tabs;
@ -283,5 +285,97 @@ pub fn tab_line(
capabilities, capabilities,
); );
prefix.append(&mut tabs_to_render); prefix.append(&mut tabs_to_render);
let current_title_len = get_current_title_len(&prefix);
if current_title_len < cols {
let mut remaining_space = cols - current_title_len;
if let Some(swap_layout_status) = swap_layout_status(
remaining_space,
active_swap_layout_name,
is_swap_layout_dirty,
mode,
&palette,
tab_separator(capabilities),
) {
remaining_space -= swap_layout_status.len;
let mut buffer = String::new();
for _ in 0..remaining_space {
buffer.push_str(&style!(palette.black, palette.black).paint(" ").to_string());
}
prefix.push(LinePart {
part: buffer,
len: remaining_space,
tab_index: None,
});
prefix.push(swap_layout_status);
}
}
prefix prefix
} }
fn swap_layout_status(
max_len: usize,
swap_layout_name: &Option<String>,
is_swap_layout_damaged: bool,
input_mode: InputMode,
palette: &Palette,
separator: &str,
) -> Option<LinePart> {
match swap_layout_name {
Some(swap_layout_name) => {
let mut swap_layout_name = format!(" {} ", swap_layout_name);
swap_layout_name.make_ascii_uppercase();
let swap_layout_name_len = swap_layout_name.len() + 3;
let (prefix_separator, swap_layout_name, suffix_separator) =
if input_mode == InputMode::Locked {
(
style!(palette.black, palette.fg).paint(separator),
style!(palette.black, palette.fg)
.italic()
.paint(&swap_layout_name),
style!(palette.fg, palette.black).paint(separator),
)
} else if is_swap_layout_damaged {
(
style!(palette.black, palette.fg).paint(separator),
style!(palette.black, palette.fg)
.bold()
.paint(&swap_layout_name),
style!(palette.fg, palette.black).paint(separator),
)
} else {
(
style!(palette.black, palette.green).paint(separator),
style!(palette.black, palette.green)
.bold()
.paint(&swap_layout_name),
style!(palette.green, palette.black).paint(separator),
)
};
let swap_layout_indicator = format!(
"{}{}{}",
prefix_separator, swap_layout_name, suffix_separator
);
let (part, full_len) = (format!("{}", swap_layout_indicator), swap_layout_name_len);
let short_len = swap_layout_name_len + 1; // 1 is the space between
if full_len <= max_len {
Some(LinePart {
part,
len: full_len,
tab_index: None,
})
} else if short_len <= max_len && input_mode != InputMode::Locked {
Some(LinePart {
part: swap_layout_indicator,
len: short_len,
tab_index: None,
})
} else {
None
}
},
None => None,
}
}

View file

@ -92,6 +92,8 @@ impl ZellijPlugin for State {
} }
let mut all_tabs: Vec<LinePart> = vec![]; let mut all_tabs: Vec<LinePart> = vec![];
let mut active_tab_index = 0; let mut active_tab_index = 0;
let mut active_swap_layout_name = None;
let mut is_swap_layout_dirty = false;
let mut is_alternate_tab = false; let mut is_alternate_tab = false;
for t in &mut self.tabs { for t in &mut self.tabs {
let mut tabname = t.name.clone(); let mut tabname = t.name.clone();
@ -102,6 +104,8 @@ impl ZellijPlugin for State {
active_tab_index = t.position; active_tab_index = t.position;
} else if t.active { } else if t.active {
active_tab_index = t.position; active_tab_index = t.position;
is_swap_layout_dirty = t.is_swap_layout_dirty;
active_swap_layout_name = t.active_swap_layout_name.clone();
} }
let tab = tab_style( let tab = tab_style(
tabname, tabname,
@ -121,6 +125,8 @@ impl ZellijPlugin for State {
self.mode_info.style.colors, self.mode_info.style.colors,
self.mode_info.capabilities, self.mode_info.capabilities,
self.mode_info.mode, self.mode_info.mode,
&active_swap_layout_name,
is_swap_layout_dirty,
); );
let mut s = String::new(); let mut s = String::new();
let mut len_cnt = 0; let mut len_cnt = 0;

View file

@ -1,9 +1,11 @@
use ansi_term::ANSIStrings; use ansi_term::{unstyled_len, ANSIStrings};
use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::actions::Action;
use zellij_tile::prelude::*; use zellij_tile::prelude::*;
use crate::color_elements; use crate::color_elements;
use crate::{action_key, get_common_modifier, TO_NORMAL}; use crate::{
action_key, action_key_group, get_common_modifier, style_key_with_modifier, TO_NORMAL,
};
use crate::{ColoredElements, LinePart}; use crate::{ColoredElements, LinePart};
struct KeyShortcut { struct KeyShortcut {
@ -232,6 +234,102 @@ fn key_indicators(
line_part line_part
} }
fn swap_layout_keycode(mode_info: &ModeInfo, palette: &Palette) -> LinePart {
let mode_keybinds = mode_info.get_mode_keybinds();
let prev_next_keys = action_key_group(
&mode_keybinds,
&[&[Action::PreviousSwapLayout], &[Action::NextSwapLayout]],
);
let prev_next_keys_indicator =
style_key_with_modifier(&prev_next_keys, palette, Some(palette.black));
let keycode = ANSIStrings(&prev_next_keys_indicator);
let len = unstyled_len(&keycode);
let part = keycode.to_string();
LinePart { part, len }
}
fn swap_layout_status(
max_len: usize,
swap_layout_name: &Option<String>,
is_swap_layout_damaged: bool,
mode_info: &ModeInfo,
colored_elements: ColoredElements,
palette: &Palette,
separator: &str,
) -> Option<LinePart> {
match swap_layout_name {
Some(swap_layout_name) => {
let mut swap_layout_name = format!(" {} ", swap_layout_name);
swap_layout_name.make_ascii_uppercase();
let keycode = swap_layout_keycode(mode_info, palette);
let swap_layout_name_len = swap_layout_name.len() + 3; // 2 for the arrow separators, one for the screen end buffer
//
macro_rules! style_swap_layout_indicator {
($style_name:ident) => {{
(
colored_elements
.$style_name
.prefix_separator
.paint(separator),
colored_elements
.$style_name
.styled_text
.paint(&swap_layout_name),
colored_elements
.$style_name
.suffix_separator
.paint(separator),
)
}};
}
let (prefix_separator, swap_layout_name, suffix_separator) =
if mode_info.mode == InputMode::Locked {
style_swap_layout_indicator!(disabled)
} else if is_swap_layout_damaged {
style_swap_layout_indicator!(unselected)
} else {
style_swap_layout_indicator!(selected)
};
let swap_layout_indicator = format!(
"{}{}{}",
prefix_separator, swap_layout_name, suffix_separator
);
let (part, full_len) = if mode_info.mode == InputMode::Locked {
(
format!("{}", swap_layout_indicator),
swap_layout_name_len, // 1 is the space between
)
} else {
(
format!(
"{}{}{}{}",
keycode,
colored_elements.superkey_prefix.paint(" "),
swap_layout_indicator,
colored_elements.superkey_prefix.paint(" ")
),
keycode.len + swap_layout_name_len + 1, // 1 is the space between
)
};
let short_len = swap_layout_name_len + 1; // 1 is the space between
if full_len <= max_len {
Some(LinePart {
part,
len: full_len,
})
} else if short_len <= max_len && mode_info.mode != InputMode::Locked {
Some(LinePart {
part: swap_layout_indicator,
len: short_len,
})
} else {
None
}
},
None => None,
}
}
/// Get the keybindings for switching `InputMode`s and `Quit` visible in status bar. /// Get the keybindings for switching `InputMode`s and `Quit` visible in status bar.
/// ///
/// Return a Vector of `Key`s where each `Key` is a shortcut to switch to some `InputMode` or Quit /// Return a Vector of `Key`s where each `Key` is a shortcut to switch to some `InputMode` or Quit
@ -351,7 +449,12 @@ fn get_key_shortcut_for_mode<'a>(
None None
} }
pub fn first_line(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { pub fn first_line(
help: &ModeInfo,
tab_info: Option<&TabInfo>,
max_len: usize,
separator: &str,
) -> LinePart {
let supports_arrow_fonts = !help.capabilities.arrow_fonts; let supports_arrow_fonts = !help.capabilities.arrow_fonts;
let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts);
let binds = &help.get_mode_keybinds(); let binds = &help.get_mode_keybinds();
@ -432,7 +535,32 @@ pub fn first_line(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart
)); ));
} }
key_indicators(max_len, &default_keys, colored_elements, separator, help) let mut key_indicators =
key_indicators(max_len, &default_keys, colored_elements, separator, help);
if key_indicators.len < max_len {
if let Some(tab_info) = tab_info {
let mut remaining_space = max_len - key_indicators.len;
if let Some(swap_layout_status) = swap_layout_status(
remaining_space,
&tab_info.active_swap_layout_name,
tab_info.is_swap_layout_dirty,
help,
colored_elements,
&help.style.colors,
separator,
) {
remaining_space -= swap_layout_status.len;
for _ in 0..remaining_space {
key_indicators.part.push_str(
&ANSIStrings(&[colored_elements.superkey_prefix.paint(" ")]).to_string(),
);
key_indicators.len += 1;
}
key_indicators.append(&swap_layout_status);
}
}
}
key_indicators
} }
#[cfg(test)] #[cfg(test)]
@ -735,7 +863,7 @@ mod tests {
..ModeInfo::default() ..ModeInfo::default()
}; };
let ret = first_line(&mode_info, 500, ">"); let ret = first_line(&mode_info, None, 500, ">");
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!( assert_eq!(
@ -759,7 +887,7 @@ mod tests {
..ModeInfo::default() ..ModeInfo::default()
}; };
let ret = first_line(&mode_info, 500, ">"); let ret = first_line(&mode_info, None, 500, ">");
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!( assert_eq!(
@ -785,7 +913,7 @@ mod tests {
..ModeInfo::default() ..ModeInfo::default()
}; };
let ret = first_line(&mode_info, 500, ">"); let ret = first_line(&mode_info, None, 500, ">");
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!( assert_eq!(
@ -812,7 +940,7 @@ mod tests {
..ModeInfo::default() ..ModeInfo::default()
}; };
let ret = first_line(&mode_info, 50, ">"); let ret = first_line(&mode_info, None, 50, ">");
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, " Ctrl + >> a >> b >> c >> d >> e >".to_string()); assert_eq!(ret, " Ctrl + >> a >> b >> c >> d >> e >".to_string());
@ -833,7 +961,7 @@ mod tests {
..ModeInfo::default() ..ModeInfo::default()
}; };
let ret = first_line(&mode_info, 30, ""); let ret = first_line(&mode_info, None, 30, "");
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, " Ctrl + a b c ".to_string()); assert_eq!(ret, " Ctrl + a b c ".to_string());

View file

@ -44,6 +44,13 @@ pub struct LinePart {
len: usize, len: usize,
} }
impl LinePart {
pub fn append(&mut self, to_append: &LinePart) {
self.part.push_str(&to_append.part);
self.len += to_append.len;
}
}
impl Display for LinePart { impl Display for LinePart {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.part) write!(f, "{}", self.part)
@ -236,7 +243,8 @@ impl ZellijPlugin for State {
"" ""
}; };
let first_line = first_line(&self.mode_info, cols, separator); let active_tab = self.tabs.iter().find(|t| t.active);
let first_line = first_line(&self.mode_info, active_tab, cols, separator);
let second_line = self.second_line(cols); let second_line = self.second_line(cols);
let background = match self.mode_info.style.colors.theme_hue { let background = match self.mode_info.style.colors.theme_hue {
@ -396,7 +404,11 @@ pub fn action_key_group(keymap: &[(Key, Vec<Action>)], actions: &[&[Action]]) ->
/// ///
/// The returned Vector of [`ANSIString`] is suitable for transformation into an [`ANSIStrings`] /// The returned Vector of [`ANSIString`] is suitable for transformation into an [`ANSIStrings`]
/// type. /// type.
pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec<ANSIString<'static>> { pub fn style_key_with_modifier(
keyvec: &[Key],
palette: &Palette,
background: Option<PaletteColor>,
) -> Vec<ANSIString<'static>> {
// Nothing to do, quit... // Nothing to do, quit...
if keyvec.is_empty() { if keyvec.is_empty() {
return vec![]; return vec![];
@ -419,13 +431,32 @@ pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec<ANSIStr
let painted_modifier = if modifier_str.is_empty() { let painted_modifier = if modifier_str.is_empty() {
Style::new().paint("") Style::new().paint("")
} else { } else {
Style::new().fg(orange_color).bold().paint(modifier_str) if let Some(background) = background {
let background = palette_match!(background);
Style::new()
.fg(orange_color)
.on(background)
.bold()
.paint(modifier_str)
} else {
Style::new().fg(orange_color).bold().paint(modifier_str)
}
}; };
ret.push(painted_modifier); ret.push(painted_modifier);
// Prints key group start // Prints key group start
let group_start_str = if no_modifier { "<" } else { " + <" }; let group_start_str = if no_modifier { "<" } else { " + <" };
ret.push(Style::new().fg(text_color).paint(group_start_str)); if let Some(background) = background {
let background = palette_match!(background);
ret.push(
Style::new()
.fg(text_color)
.on(background)
.paint(group_start_str),
);
} else {
ret.push(Style::new().fg(text_color).paint(group_start_str));
}
// Prints the keys // Prints the keys
let key = keyvec let key = keyvec
@ -451,18 +482,50 @@ pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec<ANSIStr
"←↓↑→" => "", "←↓↑→" => "",
"←→" => "", "←→" => "",
"↓↑" => "", "↓↑" => "",
"[]" => "",
_ => "|", _ => "|",
}; };
for (idx, key) in key.iter().enumerate() { for (idx, key) in key.iter().enumerate() {
if idx > 0 && !key_separator.is_empty() { if idx > 0 && !key_separator.is_empty() {
ret.push(Style::new().fg(text_color).paint(key_separator)); if let Some(background) = background {
let background = palette_match!(background);
ret.push(
Style::new()
.fg(text_color)
.on(background)
.paint(key_separator),
);
} else {
ret.push(Style::new().fg(text_color).paint(key_separator));
}
}
if let Some(background) = background {
let background = palette_match!(background);
ret.push(
Style::new()
.fg(green_color)
.on(background)
.bold()
.paint(key.clone()),
);
} else {
ret.push(Style::new().fg(green_color).bold().paint(key.clone()));
} }
ret.push(Style::new().fg(green_color).bold().paint(key.clone()));
} }
let group_end_str = ">"; let group_end_str = ">";
ret.push(Style::new().fg(text_color).paint(group_end_str)); if let Some(background) = background {
let background = palette_match!(background);
ret.push(
Style::new()
.fg(text_color)
.on(background)
.paint(group_end_str),
);
} else {
ret.push(Style::new().fg(text_color).paint(group_end_str));
}
ret ret
} }
@ -623,7 +686,7 @@ pub mod tests {
let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')]; let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<a|b|c>".to_string()) assert_eq!(ret, "<a|b|c>".to_string())
@ -639,7 +702,7 @@ pub mod tests {
]; ];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<hjkl>".to_string()) assert_eq!(ret, "<hjkl>".to_string())
@ -656,7 +719,7 @@ pub mod tests {
]; ];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<h|k|j|l>".to_string()) assert_eq!(ret, "<h|k|j|l>".to_string())
@ -672,7 +735,7 @@ pub mod tests {
]; ];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<←↓↑→>".to_string()) assert_eq!(ret, "<←↓↑→>".to_string())
@ -683,7 +746,7 @@ pub mod tests {
let keyvec = vec![Key::Char('←'), Key::Char('→')]; let keyvec = vec![Key::Char('←'), Key::Char('→')];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<←→>".to_string()) assert_eq!(ret, "<←→>".to_string())
@ -694,7 +757,7 @@ pub mod tests {
let keyvec = vec![Key::Char('↓'), Key::Char('↑')]; let keyvec = vec![Key::Char('↓'), Key::Char('↑')];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<↓↑>".to_string()) assert_eq!(ret, "<↓↑>".to_string())
@ -710,7 +773,7 @@ pub mod tests {
]; ];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "Ctrl + <a|b|c|d>".to_string()) assert_eq!(ret, "Ctrl + <a|b|c|d>".to_string())
@ -726,7 +789,7 @@ pub mod tests {
]; ];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "Alt + <a|b|c|d>".to_string()) assert_eq!(ret, "Alt + <a|b|c|d>".to_string())
@ -742,7 +805,7 @@ pub mod tests {
]; ];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "Alt + <←↓↑→>".to_string()) assert_eq!(ret, "Alt + <←↓↑→>".to_string())
@ -757,7 +820,7 @@ pub mod tests {
]; ];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<Alt+a|Ctrl+b|c>".to_string()) assert_eq!(ret, "<Alt+a|Ctrl+b|c>".to_string())
@ -780,7 +843,7 @@ pub mod tests {
]; ];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!( assert_eq!(
@ -794,7 +857,7 @@ pub mod tests {
let keyvec = vec![Key::Ctrl('\n'), Key::Ctrl(' '), Key::Ctrl('\t')]; let keyvec = vec![Key::Ctrl('\n'), Key::Ctrl(' '), Key::Ctrl('\t')];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "Ctrl + <ENTER|SPACE|TAB>".to_string()) assert_eq!(ret, "Ctrl + <ENTER|SPACE|TAB>".to_string())
@ -809,7 +872,7 @@ pub mod tests {
]; ];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "Alt + <ENTER|SPACE|TAB>".to_string()) assert_eq!(ret, "Alt + <ENTER|SPACE|TAB>".to_string())

View file

@ -30,7 +30,7 @@ fn full_length_shortcut(
let separator = if is_first_shortcut { " " } else { " / " }; let separator = if is_first_shortcut { " " } else { " / " };
let mut bits: Vec<ANSIString> = vec![Style::new().fg(text_color).paint(separator)]; let mut bits: Vec<ANSIString> = vec![Style::new().fg(text_color).paint(separator)];
bits.extend(style_key_with_modifier(&key, &palette)); bits.extend(style_key_with_modifier(&key, &palette, None));
bits.push( bits.push(
Style::new() Style::new()
.fg(text_color) .fg(text_color)
@ -179,7 +179,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> {
vec![ vec![
(s("Move focus"), s("Move"), focus_keys), (s("Move focus"), s("Move"), focus_keys),
(s("New"), s("New"), action_key(&km, &[A::NewTab(None, vec![], None), TO_NORMAL])), (s("New"), s("New"), action_key(&km, &[A::NewTab(None, vec![], None, None, None), TO_NORMAL])),
(s("Close"), s("Close"), action_key(&km, &[A::CloseTab, TO_NORMAL])), (s("Close"), s("Close"), action_key(&km, &[A::CloseTab, TO_NORMAL])),
(s("Rename"), s("Rename"), (s("Rename"), s("Rename"),
action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])),
@ -210,6 +210,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> {
&[Action::MovePane(Some(Dir::Left))], &[Action::MovePane(Some(Dir::Down))], &[Action::MovePane(Some(Dir::Left))], &[Action::MovePane(Some(Dir::Down))],
&[Action::MovePane(Some(Dir::Up))], &[Action::MovePane(Some(Dir::Right))]])), &[Action::MovePane(Some(Dir::Up))], &[Action::MovePane(Some(Dir::Right))]])),
(s("Next pane"), s("Next"), action_key(&km, &[Action::MovePane(None)])), (s("Next pane"), s("Next"), action_key(&km, &[Action::MovePane(None)])),
(s("Previous pane"), s("Previous"), action_key(&km, &[Action::MovePaneBackwards])),
]} else if mi.mode == IM::Scroll { vec![ ]} else if mi.mode == IM::Scroll { vec![
(s("Scroll"), s("Scroll"), (s("Scroll"), s("Scroll"),
action_key_group(&km, &[&[Action::ScrollDown], &[Action::ScrollUp]])), action_key_group(&km, &[&[Action::ScrollDown], &[Action::ScrollUp]])),
@ -253,7 +254,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> {
(s("Split down"), s("Down"), action_key(&km, &[A::NewPane(Some(Dir::Down), None), TO_NORMAL])), (s("Split down"), s("Down"), action_key(&km, &[A::NewPane(Some(Dir::Down), None), TO_NORMAL])),
(s("Split right"), s("Right"), action_key(&km, &[A::NewPane(Some(Dir::Right), None), TO_NORMAL])), (s("Split right"), s("Right"), action_key(&km, &[A::NewPane(Some(Dir::Right), None), TO_NORMAL])),
(s("Fullscreen"), s("Fullscreen"), action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])), (s("Fullscreen"), s("Fullscreen"), action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])),
(s("New tab"), s("New"), action_key(&km, &[A::NewTab(None, vec![], None), TO_NORMAL])), (s("New tab"), s("New"), action_key(&km, &[A::NewTab(None, vec![], None, None, None), TO_NORMAL])),
(s("Rename tab"), s("Rename"), (s("Rename tab"), s("Rename"),
action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])),
(s("Previous Tab"), s("Previous"), action_key(&km, &[A::GoToPreviousTab, TO_NORMAL])), (s("Previous Tab"), s("Previous"), action_key(&km, &[A::GoToPreviousTab, TO_NORMAL])),

View file

@ -93,8 +93,12 @@ fn add_keybinds(help: &ModeInfo) -> Vec<ANSIString> {
} }
let mut bits = vec![]; let mut bits = vec![];
bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); bits.extend(style_key_with_modifier(&to_pane, &help.style.colors, None));
bits.push(Style::new().paint(", ")); bits.push(Style::new().paint(", "));
bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); bits.extend(style_key_with_modifier(
&pane_frames,
&help.style.colors,
None,
));
bits bits
} }

View file

@ -83,8 +83,12 @@ fn add_keybinds(help: &ModeInfo) -> Vec<ANSIString> {
} }
let mut bits = vec![]; let mut bits = vec![];
bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); bits.extend(style_key_with_modifier(&to_pane, &help.style.colors, None));
bits.push(Style::new().paint(", ")); bits.push(Style::new().paint(", "));
bits.extend(style_key_with_modifier(&edit_buffer, &help.style.colors)); bits.extend(style_key_with_modifier(
&edit_buffer,
&help.style.colors,
None,
));
bits bits
} }

View file

@ -62,11 +62,12 @@ fn add_keybinds(help: &ModeInfo) -> Vec<ANSIString> {
} }
let mut bits = vec![]; let mut bits = vec![];
bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); bits.extend(style_key_with_modifier(&to_pane, &help.style.colors, None));
bits.push(Style::new().paint(", ")); bits.push(Style::new().paint(", "));
bits.extend(style_key_with_modifier( bits.extend(style_key_with_modifier(
&floating_toggle, &floating_toggle,
&help.style.colors, &help.style.colors,
None,
)); ));
bits bits
} }

View file

@ -70,8 +70,8 @@ fn add_keybinds(help: &ModeInfo) -> Vec<ANSIString> {
letters.push(key); letters.push(key);
} }
} }
let arrows = style_key_with_modifier(&arrows, &help.style.colors); let arrows = style_key_with_modifier(&arrows, &help.style.colors, None);
let letters = style_key_with_modifier(&letters, &help.style.colors); let letters = style_key_with_modifier(&letters, &help.style.colors, None);
if arrows.is_empty() && letters.is_empty() { if arrows.is_empty() && letters.is_empty() {
vec![Style::new().bold().paint("UNBOUND")] vec![Style::new().bold().paint("UNBOUND")]
} else if arrows.is_empty() || letters.is_empty() { } else if arrows.is_empty() || letters.is_empty() {

View file

@ -66,7 +66,7 @@ fn add_keybinds(help: &ModeInfo) -> Keygroups {
let new_pane = if new_pane_keys.is_empty() { let new_pane = if new_pane_keys.is_empty() {
vec![Style::new().bold().paint("UNBOUND")] vec![Style::new().bold().paint("UNBOUND")]
} else { } else {
style_key_with_modifier(&new_pane_keys, &help.style.colors) style_key_with_modifier(&new_pane_keys, &help.style.colors, None)
}; };
let mut resize_keys = action_key_group( let mut resize_keys = action_key_group(
@ -84,7 +84,7 @@ fn add_keybinds(help: &ModeInfo) -> Keygroups {
let resize = if resize_keys.is_empty() { let resize = if resize_keys.is_empty() {
vec![Style::new().bold().paint("UNBOUND")] vec![Style::new().bold().paint("UNBOUND")]
} else { } else {
style_key_with_modifier(&resize_keys, &help.style.colors) style_key_with_modifier(&resize_keys, &help.style.colors, None)
}; };
let move_focus_keys = action_key_group( let move_focus_keys = action_key_group(
@ -113,8 +113,8 @@ fn add_keybinds(help: &ModeInfo) -> Keygroups {
letters.push(key); letters.push(key);
} }
} }
let arrows = style_key_with_modifier(&arrows, &help.style.colors); let arrows = style_key_with_modifier(&arrows, &help.style.colors, None);
let letters = style_key_with_modifier(&letters, &help.style.colors); let letters = style_key_with_modifier(&letters, &help.style.colors, None);
let move_focus = if arrows.is_empty() && letters.is_empty() { let move_focus = if arrows.is_empty() && letters.is_empty() {
vec![Style::new().bold().paint("UNBOUND")] vec![Style::new().bold().paint("UNBOUND")]
} else if arrows.is_empty() || letters.is_empty() { } else if arrows.is_empty() || letters.is_empty() {

View file

@ -61,8 +61,12 @@ fn add_keybinds(help: &ModeInfo) -> Vec<ANSIString> {
} }
let mut bits = vec![]; let mut bits = vec![];
bits.extend(style_key_with_modifier(&to_tab, &help.style.colors)); bits.extend(style_key_with_modifier(&to_tab, &help.style.colors, None));
bits.push(Style::new().paint(", ")); bits.push(Style::new().paint(", "));
bits.extend(style_key_with_modifier(&sync_tabs, &help.style.colors)); bits.extend(style_key_with_modifier(
&sync_tabs,
&help.style.colors,
None,
));
bits bits
} }

View file

@ -201,6 +201,7 @@ fn read_from_channel(
Rc::new(RefCell::new(Palette::default())), Rc::new(RefCell::new(Palette::default())),
Rc::new(RefCell::new(HashMap::new())), Rc::new(RefCell::new(HashMap::new())),
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
loop { loop {
if !should_keep_running.load(Ordering::SeqCst) { if !should_keep_running.load(Ordering::SeqCst) {
@ -397,6 +398,7 @@ impl RemoteRunner {
y: 0, y: 0,
rows, rows,
cols, cols,
is_stacked: false,
}; };
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
start_zellij(&mut channel); start_zellij(&mut channel);
@ -432,6 +434,7 @@ impl RemoteRunner {
y: 0, y: 0,
rows, rows,
cols, cols,
is_stacked: false,
}; };
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
start_zellij_mirrored_session(&mut channel); start_zellij_mirrored_session(&mut channel);
@ -474,6 +477,7 @@ impl RemoteRunner {
y: 0, y: 0,
rows, rows,
cols, cols,
is_stacked: false,
}; };
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
start_zellij_in_session(&mut channel, session_name, mirrored); start_zellij_in_session(&mut channel, session_name, mirrored);
@ -509,6 +513,7 @@ impl RemoteRunner {
y: 0, y: 0,
rows, rows,
cols, cols,
is_stacked: false,
}; };
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
attach_to_existing_session(&mut channel, session_name); attach_to_existing_session(&mut channel, session_name);
@ -544,6 +549,7 @@ impl RemoteRunner {
y: 0, y: 0,
rows, rows,
cols, cols,
is_stacked: false,
}; };
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
start_zellij_without_frames(&mut channel); start_zellij_without_frames(&mut channel);
@ -580,6 +586,7 @@ impl RemoteRunner {
y: 0, y: 0,
rows, rows,
cols, cols,
is_stacked: false,
}; };
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
start_zellij_with_config(&mut channel, &remote_path.to_string_lossy()); start_zellij_with_config(&mut channel, &remote_path.to_string_lossy());

View file

@ -25,5 +25,5 @@ expression: last_snapshot
│ ││ │ │ ││ │
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1077 assertion_line: 1046
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -25,5 +25,5 @@ expression: last_snapshot
│ ││ │ │ ││ │
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.

View file

@ -25,5 +25,5 @@ expression: second_runner_snapshot
│ ││ │ │ ││ │
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
<←→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <TAB> Toggle / <ENTER> Select pane <←→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <TAB> Toggle / <ENTER> Select pane

View file

@ -25,5 +25,5 @@ expression: first_runner_snapshot
│ ││ │ │ ││ │
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1521 assertion_line: 1490
expression: second_runner_snapshot expression: second_runner_snapshot
--- ---
Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ] Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ]
@ -25,5 +25,5 @@ expression: second_runner_snapshot
│ ││ │ │ ││ │
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1520 assertion_line: 1489
expression: first_runner_snapshot expression: first_runner_snapshot
--- ---
Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ] Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ]
@ -25,5 +25,5 @@ expression: first_runner_snapshot
│ ││ │ │ ││ │
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 744 assertion_line: 745
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -25,5 +25,5 @@ expression: last_snapshot
│ ││ │ │ ││ │
│ ││ │ │ ││ │
└────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────┘ └────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 863 assertion_line: 867
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -25,5 +25,5 @@ expression: last_snapshot
│ ││ │ │ ││ │
│ ││ │ │ ││ │
└────────────────────────────────────────────────┘└────────────────────────────────────────────────┘ └────────────────────────────────────────────────┘└────────────────────────────────────────────────┘
Ctrl + g  p  t  n  h  s  o  q  Ctrl + g  p  t  n  h  s  o  q  Alt + <[]>  BASE 
QuickNav: Alt + <n> / Alt + <←↓↑→> or Alt + <hjkl> / Alt + <+|-> QuickNav: Alt + <n> / Alt + <←↓↑→> or Alt + <hjkl> / Alt + <+|->

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 274 assertion_line: 275
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -25,5 +25,5 @@ expression: last_snapshot
│ ││line20 │ │ ││line20 │
│ ││li█e21 │ │ ││li█e21 │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
<↓↑> Scroll / <PgDn|PgUp> Scroll / <d|u> Scroll / <e> Edit / <s> Search / <ENTER> Select <↓↑> Scroll / <PgDn|PgUp> Scroll / <d|u> Scroll / <e> Edit / <s> Search / <ENTER> Select

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1152 assertion_line: 1121
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -25,5 +25,5 @@ expression: last_snapshot
│ ││line18 │ │ ││line18 │
│ ││li█e19 │ │ ││li█e19 │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1998 assertion_line: 1990
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 154 assertion_line: 155
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -25,5 +25,5 @@ expression: last_snapshot
│ ││ │ │ ││ │
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1198 assertion_line: 1167
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -25,5 +25,5 @@ $ │$ █
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1802 assertion_line: 1734
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -25,5 +25,5 @@ expression: last_snapshot
│ ││ │ │ ││ │
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1683 assertion_line: 1687
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 334 assertion_line: 335
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -25,5 +25,5 @@ expression: last_snapshot
│ │ │ │
│ │ │ │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  BASE 
(FULLSCREEN): + 1 hidden panes (FULLSCREEN): + 1 hidden panes

View file

@ -108,6 +108,7 @@ pub(crate) struct SessionMetaData {
pub capabilities: PluginCapabilities, pub capabilities: PluginCapabilities,
pub client_attributes: ClientAttributes, pub client_attributes: ClientAttributes,
pub default_shell: Option<TerminalAction>, pub default_shell: Option<TerminalAction>,
pub layout: Box<Layout>,
screen_thread: Option<thread::JoinHandle<()>>, screen_thread: Option<thread::JoinHandle<()>>,
pty_thread: Option<thread::JoinHandle<()>>, pty_thread: Option<thread::JoinHandle<()>>,
plugin_thread: Option<thread::JoinHandle<()>>, plugin_thread: Option<thread::JoinHandle<()>>,
@ -346,7 +347,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
}) })
}); });
let spawn_tabs = |tab_layout, floating_panes_layout, tab_name| { let spawn_tabs = |tab_layout, floating_panes_layout, tab_name, swap_layouts| {
session_data session_data
.read() .read()
.unwrap() .unwrap()
@ -358,6 +359,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
tab_layout, tab_layout,
floating_panes_layout, floating_panes_layout,
tab_name, tab_name,
swap_layouts,
client_id, client_id,
)) ))
.unwrap() .unwrap()
@ -369,6 +371,10 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
Some(tab_layout.clone()), Some(tab_layout.clone()),
floating_panes_layout.clone(), floating_panes_layout.clone(),
tab_name, tab_name,
(
layout.swap_tiled_layouts.clone(),
layout.swap_floating_layouts.clone(),
),
); );
} }
@ -386,7 +392,15 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap(); .unwrap();
} }
} else { } else {
spawn_tabs(None, layout.floating_panes_template.clone(), None); spawn_tabs(
None,
layout.template.map(|t| t.1).clone().unwrap_or_default(),
None,
(
layout.swap_tiled_layouts.clone(),
layout.swap_floating_layouts.clone(),
),
);
} }
session_data session_data
.read() .read()
@ -751,6 +765,7 @@ fn init_session(
); );
let store = get_store(); let store = get_store();
let layout = layout.clone();
move || { move || {
plugin_thread_main( plugin_thread_main(
plugin_bus, plugin_bus,
@ -811,6 +826,7 @@ fn init_session(
capabilities, capabilities,
default_shell, default_shell,
client_attributes, client_attributes,
layout,
screen_thread: Some(screen_thread), screen_thread: Some(screen_thread),
pty_thread: Some(pty_thread), pty_thread: Some(pty_thread),
plugin_thread: Some(plugin_thread), plugin_thread: Some(plugin_thread),

View file

@ -396,10 +396,11 @@ pub struct ServerOsInputOutput {
orig_termios: Arc<Mutex<termios::Termios>>, orig_termios: Arc<Mutex<termios::Termios>>,
client_senders: Arc<Mutex<HashMap<ClientId, ClientSender>>>, client_senders: Arc<Mutex<HashMap<ClientId, ClientSender>>>,
terminal_id_to_raw_fd: Arc<Mutex<BTreeMap<u32, Option<RawFd>>>>, // A value of None means the terminal_id_to_raw_fd: Arc<Mutex<BTreeMap<u32, Option<RawFd>>>>, // A value of None means the
// terminal_id exists but is // terminal_id exists but is
// not connected to an fd (eg. // not connected to an fd (eg.
// a command pane with a // a command pane with a
// non-existing command) // non-existing command)
cached_resizes: Arc<Mutex<Option<BTreeMap<u32, (u16, u16)>>>>, // <terminal_id, (cols, rows)>
} }
// async fn in traits is not supported by rust, so dtolnay's excellent async_trait macro is being // async fn in traits is not supported by rust, so dtolnay's excellent async_trait macro is being
@ -481,6 +482,8 @@ pub trait ServerOsApi: Send + Sync {
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status
) -> Result<(RawFd, RawFd)>; ) -> Result<(RawFd, RawFd)>;
fn clear_terminal_id(&self, terminal_id: u32) -> Result<()>; fn clear_terminal_id(&self, terminal_id: u32) -> Result<()>;
fn cache_resizes(&mut self) {}
fn apply_cached_resizes(&mut self) {}
} }
impl ServerOsApi for ServerOsInputOutput { impl ServerOsApi for ServerOsInputOutput {
@ -491,6 +494,10 @@ impl ServerOsApi for ServerOsInputOutput {
id, rows, cols id, rows, cols
) )
}; };
if let Some(cached_resizes) = self.cached_resizes.lock().unwrap().as_mut() {
cached_resizes.insert(id, (cols, rows));
return Ok(());
}
match self match self
.terminal_id_to_raw_fd .terminal_id_to_raw_fd
@ -752,6 +759,19 @@ impl ServerOsApi for ServerOsInputOutput {
.remove(&terminal_id); .remove(&terminal_id);
Ok(()) Ok(())
} }
fn cache_resizes(&mut self) {
if self.cached_resizes.lock().unwrap().is_none() {
*self.cached_resizes.lock().unwrap() = Some(BTreeMap::new());
}
}
fn apply_cached_resizes(&mut self) {
let mut cached_resizes = self.cached_resizes.lock().unwrap().take();
if let Some(cached_resizes) = cached_resizes.as_mut() {
for (terminal_id, (cols, rows)) in cached_resizes.iter() {
let _ = self.set_terminal_size_using_terminal_id(*terminal_id, *cols, *rows);
}
}
}
} }
impl Clone for Box<dyn ServerOsApi> { impl Clone for Box<dyn ServerOsApi> {
@ -767,6 +787,7 @@ pub fn get_server_os_input() -> Result<ServerOsInputOutput, nix::Error> {
orig_termios, orig_termios,
client_senders: Arc::new(Mutex::new(HashMap::new())), client_senders: Arc::new(Mutex::new(HashMap::new())),
terminal_id_to_raw_fd: Arc::new(Mutex::new(BTreeMap::new())), terminal_id_to_raw_fd: Arc::new(Mutex::new(BTreeMap::new())),
cached_resizes: Arc::new(Mutex::new(None)),
}) })
} }

View file

@ -3,6 +3,7 @@ use crate::tab::Pane;
use crate::{os_input_output::ServerOsApi, panes::PaneId, ClientId}; use crate::{os_input_output::ServerOsApi, panes::PaneId, ClientId};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
#[derive(Clone)]
pub struct ActivePanes { pub struct ActivePanes {
active_panes: HashMap<ClientId, PaneId>, active_panes: HashMap<ClientId, PaneId>,
os_api: Box<dyn ServerOsApi>, os_api: Box<dyn ServerOsApi>,

View file

@ -703,6 +703,56 @@ impl<'a> FloatingPaneGrid<'a> {
.copied(); .copied();
next_index next_index
} }
pub fn next_selectable_pane_id(&self, current_pane_id: &PaneId) -> Option<PaneId> {
let panes = self.panes.borrow();
let mut panes: Vec<(PaneId, &&mut Box<dyn Pane>)> = panes
.iter()
.filter(|(_, p)| p.selectable())
.map(|(p_id, p)| (*p_id, p))
.collect();
panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| {
if a_pane.y() == b_pane.y() {
a_pane.x().cmp(&b_pane.x())
} else {
a_pane.y().cmp(&b_pane.y())
}
});
let active_pane_position = panes
.iter()
.position(|(id, _)| id == current_pane_id)
.unwrap();
let next_active_pane_id = panes
.get(active_pane_position + 1)
.or_else(|| panes.get(0))
.map(|p| p.0)
.unwrap();
Some(next_active_pane_id)
}
pub fn previous_selectable_pane_id(&self, current_pane_id: &PaneId) -> Option<PaneId> {
let panes = self.panes.borrow();
let mut panes: Vec<(PaneId, &&mut Box<dyn Pane>)> = panes
.iter()
.filter(|(_, p)| p.selectable())
.map(|(p_id, p)| (*p_id, p))
.collect();
panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| {
if a_pane.y() == b_pane.y() {
a_pane.x().cmp(&b_pane.x())
} else {
a_pane.y().cmp(&b_pane.y())
}
});
let active_pane_position = panes.iter().position(|(id, _)| id == current_pane_id)?;
let last_pane = panes.last()?;
let previous_active_pane_id = if active_pane_position == 0 {
last_pane.0
} else {
panes.get(active_pane_position - 1)?.0
};
Some(previous_active_pane_id)
}
pub fn find_room_for_new_pane(&self) -> Option<PaneGeom> { pub fn find_room_for_new_pane(&self) -> Option<PaneGeom> {
let panes = self.panes.borrow(); let panes = self.panes.borrow();
let pane_geoms: Vec<PaneGeom> = panes.values().map(|p| p.position_and_size()).collect(); let pane_geoms: Vec<PaneGeom> = panes.values().map(|p| p.position_and_size()).collect();
@ -766,6 +816,7 @@ fn half_size_middle_geom(space: &Viewport, offset: usize) -> PaneGeom {
y: space.y + (space.rows as f64 / 4.0).round() as usize + offset, y: space.y + (space.rows as f64 / 4.0).round() as usize + offset,
cols: Dimension::fixed(space.cols / 2), cols: Dimension::fixed(space.cols / 2),
rows: Dimension::fixed(space.rows / 2), rows: Dimension::fixed(space.rows / 2),
is_stacked: false,
}; };
geom.cols.set_inner(space.cols / 2); geom.cols.set_inner(space.cols / 2);
geom.rows.set_inner(space.rows / 2); geom.rows.set_inner(space.rows / 2);
@ -778,6 +829,7 @@ fn half_size_top_left_geom(space: &Viewport, offset: usize) -> PaneGeom {
y: space.y + 2 + offset, y: space.y + 2 + offset,
cols: Dimension::fixed(space.cols / 3), cols: Dimension::fixed(space.cols / 3),
rows: Dimension::fixed(space.rows / 3), rows: Dimension::fixed(space.rows / 3),
is_stacked: false,
}; };
geom.cols.set_inner(space.cols / 3); geom.cols.set_inner(space.cols / 3);
geom.rows.set_inner(space.rows / 3); geom.rows.set_inner(space.rows / 3);
@ -790,6 +842,7 @@ fn half_size_top_right_geom(space: &Viewport, offset: usize) -> PaneGeom {
y: space.y + 2 + offset, y: space.y + 2 + offset,
cols: Dimension::fixed(space.cols / 3), cols: Dimension::fixed(space.cols / 3),
rows: Dimension::fixed(space.rows / 3), rows: Dimension::fixed(space.rows / 3),
is_stacked: false,
}; };
geom.cols.set_inner(space.cols / 3); geom.cols.set_inner(space.cols / 3);
geom.rows.set_inner(space.rows / 3); geom.rows.set_inner(space.rows / 3);
@ -802,6 +855,7 @@ fn half_size_bottom_left_geom(space: &Viewport, offset: usize) -> PaneGeom {
y: ((space.y + space.rows) - (space.rows / 3) - 2).saturating_sub(offset), y: ((space.y + space.rows) - (space.rows / 3) - 2).saturating_sub(offset),
cols: Dimension::fixed(space.cols / 3), cols: Dimension::fixed(space.cols / 3),
rows: Dimension::fixed(space.rows / 3), rows: Dimension::fixed(space.rows / 3),
is_stacked: false,
}; };
geom.cols.set_inner(space.cols / 3); geom.cols.set_inner(space.cols / 3);
geom.rows.set_inner(space.rows / 3); geom.rows.set_inner(space.rows / 3);
@ -814,6 +868,7 @@ fn half_size_bottom_right_geom(space: &Viewport, offset: usize) -> PaneGeom {
y: ((space.y + space.rows) - (space.rows / 3) - 2).saturating_sub(offset), y: ((space.y + space.rows) - (space.rows / 3) - 2).saturating_sub(offset),
cols: Dimension::fixed(space.cols / 3), cols: Dimension::fixed(space.cols / 3),
rows: Dimension::fixed(space.rows / 3), rows: Dimension::fixed(space.rows / 3),
is_stacked: false,
}; };
geom.cols.set_inner(space.cols / 3); geom.cols.set_inner(space.cols / 3);
geom.rows.set_inner(space.rows / 3); geom.rows.set_inner(space.rows / 3);

View file

@ -25,7 +25,7 @@ use zellij_utils::{
data::{ModeInfo, Style}, data::{ModeInfo, Style},
errors::prelude::*, errors::prelude::*,
input::command::RunCommand, input::command::RunCommand,
input::layout::FloatingPanesLayout, input::layout::FloatingPaneLayout,
pane_size::{Dimension, Offset, PaneGeom, Size, Viewport}, pane_size::{Dimension, Offset, PaneGeom, Size, Viewport},
}; };
@ -200,6 +200,14 @@ impl FloatingPanes {
pub fn active_pane_id(&self, client_id: ClientId) -> Option<PaneId> { pub fn active_pane_id(&self, client_id: ClientId) -> Option<PaneId> {
self.active_panes.get(&client_id).copied() self.active_panes.get(&client_id).copied()
} }
pub fn active_pane_id_or_focused_pane_id(&self, client_id: Option<ClientId>) -> Option<PaneId> {
// returns the focused pane of any client_id - should be safe because the way things are
// set up at the time of writing, all clients are focused on the same floating pane due to
// z_index issues
client_id
.and_then(|client_id| self.active_panes.get(&client_id).copied())
.or_else(|| self.panes.keys().next().copied())
}
pub fn toggle_show_panes(&mut self, should_show_floating_panes: bool) { pub fn toggle_show_panes(&mut self, should_show_floating_panes: bool) {
self.show_panes = should_show_floating_panes; self.show_panes = should_show_floating_panes;
if should_show_floating_panes { if should_show_floating_panes {
@ -227,7 +235,7 @@ impl FloatingPanes {
} }
pub fn position_floating_pane_layout( pub fn position_floating_pane_layout(
&mut self, &mut self,
floating_pane_layout: &FloatingPanesLayout, floating_pane_layout: &FloatingPaneLayout,
) -> PaneGeom { ) -> PaneGeom {
let display_area = *self.display_area.borrow(); let display_area = *self.display_area.borrow();
let viewport = *self.viewport.borrow(); let viewport = *self.viewport.borrow();
@ -239,32 +247,32 @@ impl FloatingPanes {
); );
let mut position = floating_pane_grid.find_room_for_new_pane().unwrap(); // TODO: no unwrap let mut position = floating_pane_grid.find_room_for_new_pane().unwrap(); // TODO: no unwrap
if let Some(x) = &floating_pane_layout.x { if let Some(x) = &floating_pane_layout.x {
position.x = x.to_position(display_area.cols); position.x = x.to_position(viewport.cols);
} }
if let Some(y) = &floating_pane_layout.y { if let Some(y) = &floating_pane_layout.y {
position.y = y.to_position(display_area.rows); position.y = y.to_position(viewport.rows);
} }
if let Some(width) = &floating_pane_layout.width { if let Some(width) = &floating_pane_layout.width {
position.cols = Dimension::fixed(width.to_position(display_area.cols)); position.cols = Dimension::fixed(width.to_position(viewport.cols));
} }
if let Some(height) = &floating_pane_layout.height { if let Some(height) = &floating_pane_layout.height {
position.rows = Dimension::fixed(height.to_position(display_area.rows)); position.rows = Dimension::fixed(height.to_position(viewport.rows));
} }
if position.cols.as_usize() > display_area.cols { if position.cols.as_usize() > viewport.cols {
position.cols = Dimension::fixed(display_area.cols); position.cols = Dimension::fixed(viewport.cols);
} }
if position.rows.as_usize() > display_area.rows { if position.rows.as_usize() > viewport.rows {
position.rows = Dimension::fixed(display_area.rows); position.rows = Dimension::fixed(viewport.rows);
} }
if position.x + position.cols.as_usize() > display_area.cols { if position.x + position.cols.as_usize() > viewport.cols {
position.x = position position.x = position
.x .x
.saturating_sub((position.x + position.cols.as_usize()) - display_area.cols); .saturating_sub((position.x + position.cols.as_usize()) - viewport.cols);
} }
if position.y + position.rows.as_usize() > display_area.rows { if position.y + position.rows.as_usize() > viewport.rows {
position.y = position position.y = position
.y .y
.saturating_sub((position.y + position.rows.as_usize()) - display_area.rows); .saturating_sub((position.y + position.rows.as_usize()) - viewport.rows);
} }
position position
} }
@ -333,6 +341,9 @@ impl FloatingPanes {
&active_panes, &active_panes,
multiple_users_exist_in_session, multiple_users_exist_in_session,
Some(z_index + 1), // +1 because 0 is reserved for non-floating panes Some(z_index + 1), // +1 because 0 is reserved for non-floating panes
false,
false,
true,
); );
for client_id in &connected_clients { for client_id in &connected_clients {
let client_mode = self let client_mode = self
@ -570,6 +581,50 @@ impl FloatingPanes {
self.set_force_render(); self.set_force_render();
} }
} }
pub fn move_active_pane(
&mut self,
search_backwards: bool,
os_api: &mut Box<dyn ServerOsApi>,
client_id: ClientId,
) {
let active_pane_id = self.get_active_pane_id(client_id).unwrap();
let new_position_id = {
let pane_grid = FloatingPaneGrid::new(
&mut self.panes,
&mut self.desired_pane_positions,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
if search_backwards {
pane_grid.previous_selectable_pane_id(&active_pane_id)
} else {
pane_grid.next_selectable_pane_id(&active_pane_id)
}
};
if let Some(new_position_id) = new_position_id {
let current_position = self.panes.get(&active_pane_id).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&new_position_id).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
new_position.set_should_render(true);
let current_position = self.panes.get_mut(&active_pane_id).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
current_position.set_should_render(true);
let _ = self.set_pane_frames(os_api);
}
}
pub fn move_clients_out_of_pane(&mut self, pane_id: PaneId) { pub fn move_clients_out_of_pane(&mut self, pane_id: PaneId) {
let active_panes: Vec<(ClientId, PaneId)> = self let active_panes: Vec<(ClientId, PaneId)> = self
.active_panes .active_panes
@ -735,6 +790,17 @@ impl FloatingPanes {
pub fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { pub fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
self.panes.iter() self.panes.iter()
} }
pub fn visible_panes_count(&self) -> usize {
self.panes.len()
}
pub fn drain(&mut self) -> BTreeMap<PaneId, Box<dyn Pane>> {
self.z_indices.clear();
self.desired_pane_positions.clear();
match self.panes.iter().next().map(|(pid, _p)| *pid) {
Some(first_pid) => self.panes.split_off(&first_pid),
None => BTreeMap::new(),
}
}
fn move_clients_between_panes(&mut self, from_pane_id: PaneId, to_pane_id: PaneId) { fn move_clients_between_panes(&mut self, from_pane_id: PaneId, to_pane_id: PaneId) {
let clients_in_pane: Vec<ClientId> = self let clients_in_pane: Vec<ClientId> = self
.active_panes .active_panes
@ -748,4 +814,36 @@ impl FloatingPanes {
.insert(client_id, to_pane_id, &mut self.panes); .insert(client_id, to_pane_id, &mut self.panes);
} }
} }
pub fn reapply_pane_focus(&mut self) {
if let Some(focused_pane) = self.first_active_floating_pane_id() {
// floating pane focus is the same for all clients
self.focus_pane_for_all_clients(focused_pane);
}
}
pub fn switch_active_pane_with(&mut self, os_api: &mut Box<dyn ServerOsApi>, pane_id: PaneId) {
if let Some(active_pane_id) = self.first_active_floating_pane_id() {
let current_position = self.panes.get(&active_pane_id).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&pane_id).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, os_api, self.senders).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(&active_pane_id).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, os_api, self.senders).unwrap();
current_position.set_should_render(true);
self.focus_pane_for_all_clients(active_pane_id);
}
}
} }

View file

@ -16,6 +16,7 @@ use zellij_utils::{
channels::SenderWithContext, channels::SenderWithContext,
data::{Event, InputMode, Mouse, Palette, PaletteColor, Style}, data::{Event, InputMode, Mouse, Palette, PaletteColor, Style},
errors::prelude::*, errors::prelude::*,
input::layout::Run,
pane_size::PaneGeom, pane_size::PaneGeom,
shared::make_terminal_title, shared::make_terminal_title,
vte, vte,
@ -65,6 +66,7 @@ pub(crate) struct PluginPane {
frame: HashMap<ClientId, PaneFrame>, frame: HashMap<ClientId, PaneFrame>,
borderless: bool, borderless: bool,
pane_frame_color_override: Option<(PaletteColor, Option<String>)>, pane_frame_color_override: Option<(PaletteColor, Option<String>)>,
invoked_with: Option<Run>,
} }
impl PluginPane { impl PluginPane {
@ -80,6 +82,7 @@ impl PluginPane {
link_handler: Rc<RefCell<LinkHandler>>, link_handler: Rc<RefCell<LinkHandler>>,
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>, character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
style: Style, style: Style,
invoked_with: Option<Run>,
) -> Self { ) -> Self {
Self { Self {
pid, pid,
@ -104,6 +107,7 @@ impl PluginPane {
grids: HashMap::new(), grids: HashMap::new(),
style, style,
pane_frame_color_override: None, pane_frame_color_override: None,
invoked_with,
} }
} }
} }
@ -222,6 +226,11 @@ impl Pane for PluginPane {
if self.should_render.get(&client_id).copied().unwrap_or(false) { if self.should_render.get(&client_id).copied().unwrap_or(false) {
let content_x = self.get_content_x(); let content_x = self.get_content_x();
let content_y = self.get_content_y(); let content_y = self.get_content_y();
let rows = self.get_content_rows();
let columns = self.get_content_columns();
if rows < 1 || columns < 1 {
return Ok(None);
}
if let Some(grid) = self.grids.get_mut(&client_id) { if let Some(grid) = self.grids.get_mut(&client_id) {
match grid.render(content_x, content_y, &self.style) { match grid.render(content_x, content_y, &self.style) {
Ok(rendered_assets) => { Ok(rendered_assets) => {
@ -265,8 +274,15 @@ impl Pane for PluginPane {
self.pane_name.clone() self.pane_name.clone()
}; };
let mut frame_geom = self.current_geom();
if !frame_params.should_draw_pane_frames {
// in this case the width of the frame needs not include the pane corners
frame_geom
.cols
.set_inner(frame_geom.cols.as_usize().saturating_sub(1));
}
let mut frame = PaneFrame::new( let mut frame = PaneFrame::new(
self.current_geom().into(), frame_geom.into(),
grid.scrollback_position_and_length(), grid.scrollback_position_and_length(),
pane_title, pane_title,
frame_params, frame_params,
@ -491,6 +507,12 @@ impl Pane for PluginPane {
.as_ref() .as_ref()
.map(|(color, _text)| *color) .map(|(color, _text)| *color)
} }
fn invoked_with(&self) -> &Option<Run> {
&self.invoked_with
}
fn set_title(&mut self, title: String) {
self.pane_title = title;
}
} }
impl PluginPane { impl PluginPane {

View file

@ -18,6 +18,7 @@ use zellij_utils::pane_size::Offset;
use zellij_utils::{ use zellij_utils::{
data::{InputMode, Palette, PaletteColor, Style}, data::{InputMode, Palette, PaletteColor, Style},
errors::prelude::*, errors::prelude::*,
input::layout::Run,
pane_size::PaneGeom, pane_size::PaneGeom,
pane_size::SizeInPixels, pane_size::SizeInPixels,
position::Position, position::Position,
@ -111,6 +112,7 @@ pub struct TerminalPane {
banner: Option<String>, // a banner to be rendered inside this TerminalPane, used for panes banner: Option<String>, // a banner to be rendered inside this TerminalPane, used for panes
// held on startup and can possibly be used to display some errors // held on startup and can possibly be used to display some errors
pane_frame_color_override: Option<(PaletteColor, Option<String>)>, pane_frame_color_override: Option<(PaletteColor, Option<String>)>,
invoked_with: Option<Run>,
} }
impl Pane for TerminalPane { impl Pane for TerminalPane {
@ -165,6 +167,10 @@ impl Pane for TerminalPane {
} }
fn cursor_coordinates(&self) -> Option<(usize, usize)> { fn cursor_coordinates(&self) -> Option<(usize, usize)> {
// (x, y) // (x, y)
if self.get_content_rows() < 1 || self.get_content_columns() < 1 {
// do not render cursor if there's no room for it
return None;
}
let Offset { top, left, .. } = self.content_offset; let Offset { top, left, .. } = self.content_offset;
self.grid self.grid
.cursor_coordinates() .cursor_coordinates()
@ -283,6 +289,11 @@ impl Pane for TerminalPane {
if self.should_render() { if self.should_render() {
let content_x = self.get_content_x(); let content_x = self.get_content_x();
let content_y = self.get_content_y(); let content_y = self.get_content_y();
let rows = self.get_content_rows();
let columns = self.get_content_columns();
if rows < 1 || columns < 1 {
return Ok(None);
}
match self.grid.render(content_x, content_y, &self.style) { match self.grid.render(content_x, content_y, &self.style) {
Ok(rendered_assets) => { Ok(rendered_assets) => {
self.set_should_render(false); self.set_should_render(false);
@ -347,8 +358,15 @@ impl Pane for TerminalPane {
self.pane_name.clone() self.pane_name.clone()
}; };
let mut frame_geom = self.current_geom();
if !frame_params.should_draw_pane_frames {
// in this case the width of the frame needs not include the pane corners
frame_geom
.cols
.set_inner(frame_geom.cols.as_usize().saturating_sub(1));
}
let mut frame = PaneFrame::new( let mut frame = PaneFrame::new(
self.current_geom().into(), frame_geom.into(),
self.grid.scrollback_position_and_length(), self.grid.scrollback_position_and_length(),
pane_title, pane_title,
frame_params, frame_params,
@ -690,6 +708,12 @@ impl Pane for TerminalPane {
.as_ref() .as_ref()
.map(|(color, _text)| *color) .map(|(color, _text)| *color)
} }
fn invoked_with(&self) -> &Option<Run> {
&self.invoked_with
}
fn set_title(&mut self, title: String) {
self.pane_title = title;
}
} }
impl TerminalPane { impl TerminalPane {
@ -706,6 +730,7 @@ impl TerminalPane {
terminal_emulator_colors: Rc<RefCell<Palette>>, terminal_emulator_colors: Rc<RefCell<Palette>>,
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>, terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
initial_pane_title: Option<String>, initial_pane_title: Option<String>,
invoked_with: Option<Run>,
) -> TerminalPane { ) -> TerminalPane {
let initial_pane_title = let initial_pane_title =
initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index)); initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index));
@ -739,6 +764,7 @@ impl TerminalPane {
is_held: None, is_held: None,
banner: None, banner: None,
pane_frame_color_override: None, pane_frame_color_override: None,
invoked_with,
} }
} }
pub fn get_x(&self) -> usize { pub fn get_x(&self) -> usize {
@ -780,6 +806,10 @@ impl TerminalPane {
} }
pub fn cursor_coordinates(&self) -> Option<(usize, usize)> { pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
// (x, y) // (x, y)
if self.get_content_rows() < 1 || self.get_content_columns() < 1 {
// do not render cursor if there's no room for it
return None;
}
self.grid.cursor_coordinates() self.grid.cursor_coordinates()
} }
fn render_first_run_banner(&mut self) { fn render_first_run_banner(&mut self) {

View file

@ -1,4 +1,5 @@
mod pane_resizer; mod pane_resizer;
mod stacked_panes;
mod tiled_pane_grid; mod tiled_pane_grid;
use crate::resize_pty; use crate::resize_pty;
@ -15,6 +16,7 @@ use crate::{
ui::pane_contents_and_ui::PaneContentsAndUi, ui::pane_contents_and_ui::PaneContentsAndUi,
ClientId, ClientId,
}; };
use stacked_panes::StackedPanes;
use zellij_utils::{ use zellij_utils::{
data::{ModeInfo, ResizeStrategy, Style}, data::{ModeInfo, ResizeStrategy, Style},
errors::prelude::*, errors::prelude::*,
@ -249,6 +251,10 @@ impl TiledPanes {
self.set_pane_frames(self.draw_pane_frames); self.set_pane_frames(self.draw_pane_frames);
} }
pub fn reapply_pane_frames(&mut self) {
// same as set_pane_frames except it reapplies the current situation
self.set_pane_frames(self.draw_pane_frames);
}
pub fn set_pane_frames(&mut self, draw_pane_frames: bool) { pub fn set_pane_frames(&mut self, draw_pane_frames: bool) {
self.draw_pane_frames = draw_pane_frames; self.draw_pane_frames = draw_pane_frames;
let viewport = *self.viewport.borrow(); let viewport = *self.viewport.borrow();
@ -276,7 +282,12 @@ impl TiledPanes {
let position_and_size = pane.current_geom(); let position_and_size = pane.current_geom();
let (pane_columns_offset, pane_rows_offset) = let (pane_columns_offset, pane_rows_offset) =
pane_content_offset(&position_and_size, &viewport); pane_content_offset(&position_and_size, &viewport);
pane.set_content_offset(Offset::shift(pane_rows_offset, pane_columns_offset)); if !draw_pane_frames && pane.current_geom().is_stacked {
// stacked panes should always leave 1 top row for a title
pane.set_content_offset(Offset::shift_right_and_top(pane_columns_offset, 1));
} else {
pane.set_content_offset(Offset::shift(pane_rows_offset, pane_columns_offset));
}
} }
resize_pty!(pane, self.os_api, self.senders).unwrap(); resize_pty!(pane, self.os_api, self.senders).unwrap();
@ -287,7 +298,9 @@ impl TiledPanes {
if let Some(active_pane_id) = &self.active_panes.get(&client_id) { if let Some(active_pane_id) = &self.active_panes.get(&client_id) {
if let Some(active_pane) = self.panes.get_mut(active_pane_id) { if let Some(active_pane) = self.panes.get_mut(active_pane_id) {
let full_pane_size = active_pane.position_and_size(); let full_pane_size = active_pane.position_and_size();
if full_pane_size.rows.as_usize() < MIN_TERMINAL_HEIGHT * 2 { if full_pane_size.rows.as_usize() < MIN_TERMINAL_HEIGHT * 2
|| full_pane_size.is_stacked
{
return false; return false;
} else { } else {
return split(SplitDirection::Horizontal, &full_pane_size).is_some(); return split(SplitDirection::Horizontal, &full_pane_size).is_some();
@ -300,7 +313,9 @@ impl TiledPanes {
if let Some(active_pane_id) = &self.active_panes.get(&client_id) { if let Some(active_pane_id) = &self.active_panes.get(&client_id) {
if let Some(active_pane) = self.panes.get_mut(active_pane_id) { if let Some(active_pane) = self.panes.get_mut(active_pane_id) {
let full_pane_size = active_pane.position_and_size(); let full_pane_size = active_pane.position_and_size();
if full_pane_size.cols.as_usize() < MIN_TERMINAL_WIDTH * 2 { if full_pane_size.cols.as_usize() < MIN_TERMINAL_WIDTH * 2
|| full_pane_size.is_stacked
{
return false; return false;
} }
return split(SplitDirection::Vertical, &full_pane_size).is_some(); return split(SplitDirection::Vertical, &full_pane_size).is_some();
@ -312,7 +327,7 @@ impl TiledPanes {
let active_pane_id = &self.active_panes.get(&client_id).unwrap(); let active_pane_id = &self.active_panes.get(&client_id).unwrap();
let active_pane = self.panes.get(active_pane_id).unwrap(); let active_pane = self.panes.get(active_pane_id).unwrap();
let full_pane_size = active_pane.position_and_size(); let full_pane_size = active_pane.position_and_size();
if full_pane_size.rows.is_fixed() { if full_pane_size.rows.is_fixed() || full_pane_size.is_stacked {
return false; return false;
} }
if split(SplitDirection::Horizontal, &full_pane_size).is_some() { if split(SplitDirection::Horizontal, &full_pane_size).is_some() {
@ -343,7 +358,7 @@ impl TiledPanes {
let active_pane_id = &self.active_panes.get(&client_id).unwrap(); let active_pane_id = &self.active_panes.get(&client_id).unwrap();
let active_pane = self.panes.get(active_pane_id).unwrap(); let active_pane = self.panes.get(active_pane_id).unwrap();
let full_pane_size = active_pane.position_and_size(); let full_pane_size = active_pane.position_and_size();
if full_pane_size.cols.is_fixed() { if full_pane_size.cols.is_fixed() || full_pane_size.is_stacked {
return false; return false;
} }
if split(SplitDirection::Vertical, &full_pane_size).is_some() { if split(SplitDirection::Vertical, &full_pane_size).is_some() {
@ -370,7 +385,83 @@ impl TiledPanes {
self.relayout(SplitDirection::Horizontal); self.relayout(SplitDirection::Horizontal);
} }
} }
pub fn focus_pane_for_all_clients(&mut self, pane_id: PaneId) {
let connected_clients: Vec<ClientId> =
self.connected_clients.borrow().iter().copied().collect();
for client_id in connected_clients {
if self
.panes
.get(&pane_id)
.map(|p| p.current_geom().is_stacked)
.unwrap_or(false)
{
let _ = StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.focus_pane(&pane_id);
}
self.active_panes
.insert(client_id, pane_id, &mut self.panes);
self.set_pane_active_at(pane_id);
}
self.set_force_render();
self.reapply_pane_frames();
}
pub fn reapply_pane_focus(&mut self) {
let connected_clients: Vec<ClientId> =
self.connected_clients.borrow().iter().copied().collect();
for client_id in connected_clients {
match &self.active_panes.get(&client_id).copied() {
Some(pane_id) => {
if self
.panes
.get(&pane_id)
.map(|p| p.current_geom().is_stacked)
.unwrap_or(false)
{
let _ =
StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.focus_pane(&pane_id);
}
self.active_panes
.insert(client_id, *pane_id, &mut self.panes);
self.set_pane_active_at(*pane_id);
},
None => {
if let Some(first_pane_id) = self.first_selectable_pane_id() {
let pane_id = first_pane_id; // TODO: combine with above
if self
.panes
.get(&pane_id)
.map(|p| p.current_geom().is_stacked)
.unwrap_or(false)
{
let _ = StackedPanes::new_from_btreemap(
&mut self.panes,
&self.panes_to_hide,
)
.focus_pane(&pane_id);
}
self.active_panes
.insert(client_id, pane_id, &mut self.panes);
self.set_pane_active_at(pane_id);
}
},
}
}
self.set_force_render();
self.reapply_pane_frames();
}
pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) { pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
if self
.panes
.get(&pane_id)
.map(|p| p.current_geom().is_stacked)
.unwrap_or(false)
{
let _ = StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.focus_pane(&pane_id);
self.reapply_pane_frames();
}
self.active_panes self.active_panes
.insert(client_id, pane_id, &mut self.panes); .insert(client_id, pane_id, &mut self.panes);
if self.session_is_mirrored { if self.session_is_mirrored {
@ -384,6 +475,34 @@ impl TiledPanes {
} }
self.reset_boundaries(); self.reset_boundaries();
} }
pub fn focus_pane_at_position(&mut self, position_and_size: PaneGeom, client_id: ClientId) {
if let Some(pane_id) = self
.panes
.iter()
.find(|(_pid, pane)| pane.position_and_size() == position_and_size)
.map(|(pid, _p)| *pid)
{
if let Some(currently_active_pane_id) = self.active_panes.get(&client_id) {
let prev_geom = {
if let Some(currently_focused_pane) =
self.panes.get_mut(currently_active_pane_id)
{
let prev_geom = currently_focused_pane.position_and_size();
currently_focused_pane.set_geom(position_and_size);
Some(prev_geom)
} else {
None
}
};
if let Some(prev_geom) = prev_geom {
if let Some(previous_pane) = self.panes.get_mut(&pane_id) {
previous_pane.set_geom(prev_geom);
self.reset_boundaries();
}
}
}
}
}
pub fn focus_pane_if_client_not_focused(&mut self, pane_id: PaneId, client_id: ClientId) { pub fn focus_pane_if_client_not_focused(&mut self, pane_id: PaneId, client_id: ClientId) {
if self.active_panes.get(&client_id).is_none() { if self.active_panes.get(&client_id).is_none() {
self.focus_pane(pane_id, client_id) self.focus_pane(pane_id, client_id)
@ -448,8 +567,20 @@ impl TiledPanes {
.map(|(client_id, pane_id)| (*client_id, *pane_id)) .map(|(client_id, pane_id)| (*client_id, *pane_id))
.collect() .collect()
}; };
let (stacked_pane_ids_under_flexible_pane, stacked_pane_ids_over_flexible_pane) = {
// TODO: do not recalculate this every time on render
StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.stacked_pane_ids_under_and_over_flexible_panes()
.unwrap() // TODO: no unwrap
};
for (kind, pane) in self.panes.iter_mut() { for (kind, pane) in self.panes.iter_mut() {
if !self.panes_to_hide.contains(&pane.pid()) { if !self.panes_to_hide.contains(&pane.pid()) {
let pane_is_stacked_under =
stacked_pane_ids_under_flexible_pane.contains(&pane.pid());
let pane_is_stacked_over =
stacked_pane_ids_over_flexible_pane.contains(&pane.pid());
let should_draw_pane_frames = self.draw_pane_frames;
let pane_is_stacked = pane.current_geom().is_stacked;
let mut pane_contents_and_ui = PaneContentsAndUi::new( let mut pane_contents_and_ui = PaneContentsAndUi::new(
pane, pane,
output, output,
@ -457,6 +588,9 @@ impl TiledPanes {
&active_panes, &active_panes,
multiple_users_exist_in_session, multiple_users_exist_in_session,
None, None,
pane_is_stacked_under,
pane_is_stacked_over,
should_draw_pane_frames,
); );
for client_id in &connected_clients { for client_id in &connected_clients {
let client_mode = self let client_mode = self
@ -476,6 +610,22 @@ impl TiledPanes {
pane_contents_and_ui pane_contents_and_ui
.render_pane_frame(*client_id, client_mode, self.session_is_mirrored) .render_pane_frame(*client_id, client_mode, self.session_is_mirrored)
.with_context(err_context)?; .with_context(err_context)?;
} else if pane_is_stacked {
// if we have no pane frames but the pane is stacked, we need to render its
// frame which will amount to only rendering the title line
pane_contents_and_ui
.render_pane_frame(*client_id, client_mode, self.session_is_mirrored)
.with_context(err_context)?;
// we also need to render its boundaries as normal
let boundaries = client_id_to_boundaries
.entry(*client_id)
.or_insert_with(|| Boundaries::new(*self.viewport.borrow()));
pane_contents_and_ui.render_pane_boundaries(
*client_id,
client_mode,
boundaries,
self.session_is_mirrored,
);
} else { } else {
let boundaries = client_id_to_boundaries let boundaries = client_id_to_boundaries
.entry(*client_id) .entry(*client_id)
@ -542,6 +692,8 @@ impl TiledPanes {
Err(e) => match e.downcast_ref::<ZellijError>() { Err(e) => match e.downcast_ref::<ZellijError>() {
Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout
_ => { _ => {
// display area still changed, even if we had an error
display_area.cols = cols;
Err::<(), _>(anyError::msg(e)) Err::<(), _>(anyError::msg(e))
.context("failed to resize tab horizontally") .context("failed to resize tab horizontally")
.non_fatal(); .non_fatal();
@ -557,6 +709,8 @@ impl TiledPanes {
Err(e) => match e.downcast_ref::<ZellijError>() { Err(e) => match e.downcast_ref::<ZellijError>() {
Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout
_ => { _ => {
// display area still changed, even if we had an error
display_area.rows = rows;
Err::<(), _>(anyError::msg(e)) Err::<(), _>(anyError::msg(e))
.context("failed to resize tab vertically") .context("failed to resize tab vertically")
.non_fatal(); .non_fatal();
@ -629,13 +783,26 @@ impl TiledPanes {
let connected_clients: Vec<ClientId> = let connected_clients: Vec<ClientId> =
{ self.connected_clients.borrow().iter().copied().collect() }; { self.connected_clients.borrow().iter().copied().collect() };
let active_pane_id = self.get_active_pane_id(client_id).unwrap(); let active_pane_id = self.get_active_pane_id(client_id).unwrap();
let pane_grid = TiledPaneGrid::new( let next_active_pane_id = {
&mut self.panes, let pane_grid = TiledPaneGrid::new(
&self.panes_to_hide, &mut self.panes,
*self.display_area.borrow(), &self.panes_to_hide,
*self.viewport.borrow(), *self.display_area.borrow(),
); *self.viewport.borrow(),
let next_active_pane_id = pane_grid.next_selectable_pane_id(&active_pane_id); );
pane_grid.next_selectable_pane_id(&active_pane_id)
};
if self
.panes
.get(&next_active_pane_id)
.map(|p| p.current_geom().is_stacked)
.unwrap_or(false)
{
let _ = StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.focus_pane(&next_active_pane_id);
self.reapply_pane_frames();
}
for client_id in connected_clients { for client_id in connected_clients {
self.active_panes self.active_panes
.insert(client_id, next_active_pane_id, &mut self.panes); .insert(client_id, next_active_pane_id, &mut self.panes);
@ -647,13 +814,26 @@ impl TiledPanes {
let connected_clients: Vec<ClientId> = let connected_clients: Vec<ClientId> =
{ self.connected_clients.borrow().iter().copied().collect() }; { self.connected_clients.borrow().iter().copied().collect() };
let active_pane_id = self.get_active_pane_id(client_id).unwrap(); let active_pane_id = self.get_active_pane_id(client_id).unwrap();
let pane_grid = TiledPaneGrid::new( let next_active_pane_id = {
&mut self.panes, let pane_grid = TiledPaneGrid::new(
&self.panes_to_hide, &mut self.panes,
*self.display_area.borrow(), &self.panes_to_hide,
*self.viewport.borrow(), *self.display_area.borrow(),
); *self.viewport.borrow(),
let next_active_pane_id = pane_grid.previous_selectable_pane_id(&active_pane_id); );
pane_grid.previous_selectable_pane_id(&active_pane_id)
};
if self
.panes
.get(&next_active_pane_id)
.map(|p| p.current_geom().is_stacked)
.unwrap_or(false)
{
let _ = StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.focus_pane(&next_active_pane_id);
self.reapply_pane_frames();
}
for client_id in connected_clients { for client_id in connected_clients {
self.active_panes self.active_panes
.insert(client_id, next_active_pane_id, &mut self.panes); .insert(client_id, next_active_pane_id, &mut self.panes);
@ -716,13 +896,15 @@ impl TiledPanes {
pub fn move_focus_down(&mut self, client_id: ClientId) -> bool { pub fn move_focus_down(&mut self, client_id: ClientId) -> bool {
match self.get_active_pane_id(client_id) { match self.get_active_pane_id(client_id) {
Some(active_pane_id) => { Some(active_pane_id) => {
let pane_grid = TiledPaneGrid::new( let mut pane_grid = TiledPaneGrid::new(
&mut self.panes, &mut self.panes,
&self.panes_to_hide, &self.panes_to_hide,
*self.display_area.borrow(), *self.display_area.borrow(),
*self.viewport.borrow(), *self.viewport.borrow(),
); );
let next_index = pane_grid.next_selectable_pane_id_below(&active_pane_id); let next_index = pane_grid
.next_selectable_pane_id_below(&active_pane_id)
.or_else(|| pane_grid.progress_stack_down_if_in_stack(&active_pane_id));
match next_index { match next_index {
Some(p) => { Some(p) => {
// render previously active pane so that its frame does not remain actively // render previously active pane so that its frame does not remain actively
@ -732,12 +914,16 @@ impl TiledPanes {
.get_mut(self.active_panes.get(&client_id).unwrap()) .get_mut(self.active_panes.get(&client_id).unwrap())
.unwrap(); .unwrap();
let previously_active_pane_is_stacked =
previously_active_pane.current_geom().is_stacked;
previously_active_pane.set_should_render(true); previously_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been // we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor) // there before (eg. another user's cursor)
previously_active_pane.render_full_viewport(); previously_active_pane.render_full_viewport();
let next_active_pane = self.panes.get_mut(&p).unwrap(); let next_active_pane = self.panes.get_mut(&p).unwrap();
let next_active_pane_is_stacked =
next_active_pane.current_geom().is_stacked;
next_active_pane.set_should_render(true); next_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been // we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor) // there before (eg. another user's cursor)
@ -745,6 +931,13 @@ impl TiledPanes {
self.focus_pane(p, client_id); self.focus_pane(p, client_id);
self.set_pane_active_at(p); self.set_pane_active_at(p);
if previously_active_pane_is_stacked || next_active_pane_is_stacked {
// we do this because a stack pane focus change also changes its
// geometry and we need to let the pty know about this (like in a
// normal size change)
self.focus_pane_for_all_clients(p); // TODO: for all client *in stack*
self.reapply_pane_frames();
}
true true
}, },
@ -757,13 +950,15 @@ impl TiledPanes {
pub fn move_focus_up(&mut self, client_id: ClientId) -> bool { pub fn move_focus_up(&mut self, client_id: ClientId) -> bool {
match self.get_active_pane_id(client_id) { match self.get_active_pane_id(client_id) {
Some(active_pane_id) => { Some(active_pane_id) => {
let pane_grid = TiledPaneGrid::new( let mut pane_grid = TiledPaneGrid::new(
&mut self.panes, &mut self.panes,
&self.panes_to_hide, &self.panes_to_hide,
*self.display_area.borrow(), *self.display_area.borrow(),
*self.viewport.borrow(), *self.viewport.borrow(),
); );
let next_index = pane_grid.next_selectable_pane_id_above(&active_pane_id); let next_index = pane_grid
.next_selectable_pane_id_above(&active_pane_id)
.or_else(|| pane_grid.progress_stack_up_if_in_stack(&active_pane_id));
match next_index { match next_index {
Some(p) => { Some(p) => {
// render previously active pane so that its frame does not remain actively // render previously active pane so that its frame does not remain actively
@ -773,12 +968,16 @@ impl TiledPanes {
.get_mut(self.active_panes.get(&client_id).unwrap()) .get_mut(self.active_panes.get(&client_id).unwrap())
.unwrap(); .unwrap();
let previously_active_pane_is_stacked =
previously_active_pane.current_geom().is_stacked;
previously_active_pane.set_should_render(true); previously_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been // we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor) // there before (eg. another user's cursor)
previously_active_pane.render_full_viewport(); previously_active_pane.render_full_viewport();
let next_active_pane = self.panes.get_mut(&p).unwrap(); let next_active_pane = self.panes.get_mut(&p).unwrap();
let next_active_pane_is_stacked =
next_active_pane.current_geom().is_stacked;
next_active_pane.set_should_render(true); next_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been // we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor) // there before (eg. another user's cursor)
@ -786,6 +985,13 @@ impl TiledPanes {
self.focus_pane(p, client_id); self.focus_pane(p, client_id);
self.set_pane_active_at(p); self.set_pane_active_at(p);
if previously_active_pane_is_stacked || next_active_pane_is_stacked {
// we do this because a stack pane focus change also changes its
// geometry and we need to let the pty know about this (like in a
// normal size change)
self.focus_pane_for_all_clients(p); // TODO: for all client *in stack*
self.reapply_pane_frames();
}
true true
}, },
@ -836,15 +1042,66 @@ impl TiledPanes {
None => false, None => false,
} }
} }
pub fn move_active_pane(&mut self, client_id: ClientId) { pub fn switch_active_pane_with(&mut self, pane_id: PaneId) {
if let Some(active_pane_id) = self.first_active_pane_id() {
if let PaneId::Plugin(_) = active_pane_id {
// we do not implicitly change the location of plugin panes
// TODO: we might want to make this configurable through a layout property or a
// plugin API
return;
}
let current_position = self.panes.get(&active_pane_id).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&pane_id).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api, self.senders).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(&active_pane_id).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api, self.senders).unwrap();
current_position.set_should_render(true);
self.focus_pane_for_all_clients(active_pane_id);
self.set_pane_frames(self.draw_pane_frames);
}
}
pub fn move_active_pane(&mut self, search_backwards: bool, client_id: ClientId) {
let active_pane_id = self.get_active_pane_id(client_id).unwrap(); let active_pane_id = self.get_active_pane_id(client_id).unwrap();
let pane_grid = TiledPaneGrid::new(
&mut self.panes, let new_position_id = {
&self.panes_to_hide, let pane_grid = TiledPaneGrid::new(
*self.display_area.borrow(), &mut self.panes,
*self.viewport.borrow(), &self.panes_to_hide,
); *self.display_area.borrow(),
let new_position_id = pane_grid.next_selectable_pane_id(&active_pane_id); *self.viewport.borrow(),
);
if search_backwards {
pane_grid.previous_selectable_pane_id(&active_pane_id)
} else {
pane_grid.next_selectable_pane_id(&active_pane_id)
}
};
if self
.panes
.get(&new_position_id)
.map(|p| p.current_geom().is_stacked)
.unwrap_or(false)
{
let _ = StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.focus_pane(&new_position_id);
self.reapply_pane_frames();
}
let current_position = self.panes.get(&active_pane_id).unwrap(); let current_position = self.panes.get(&active_pane_id).unwrap();
let prev_geom = current_position.position_and_size(); let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override(); let prev_geom_override = current_position.geom_override();
@ -870,13 +1127,15 @@ impl TiledPanes {
} }
pub fn move_active_pane_down(&mut self, client_id: ClientId) { pub fn move_active_pane_down(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) { if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let pane_grid = TiledPaneGrid::new( let mut pane_grid = TiledPaneGrid::new(
&mut self.panes, &mut self.panes,
&self.panes_to_hide, &self.panes_to_hide,
*self.display_area.borrow(), *self.display_area.borrow(),
*self.viewport.borrow(), *self.viewport.borrow(),
); );
let next_index = pane_grid.next_selectable_pane_id_below(&active_pane_id); let next_index = pane_grid
.next_selectable_pane_id_below(&active_pane_id)
.or_else(|| pane_grid.progress_stack_down_if_in_stack(&active_pane_id));
if let Some(p) = next_index { if let Some(p) = next_index {
let active_pane_id = self.active_panes.get(&client_id).unwrap(); let active_pane_id = self.active_panes.get(&client_id).unwrap();
let current_position = self.panes.get(active_pane_id).unwrap(); let current_position = self.panes.get(active_pane_id).unwrap();
@ -978,13 +1237,15 @@ impl TiledPanes {
} }
pub fn move_active_pane_up(&mut self, client_id: ClientId) { pub fn move_active_pane_up(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) { if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let pane_grid = TiledPaneGrid::new( let mut pane_grid = TiledPaneGrid::new(
&mut self.panes, &mut self.panes,
&self.panes_to_hide, &self.panes_to_hide,
*self.display_area.borrow(), *self.display_area.borrow(),
*self.viewport.borrow(), *self.viewport.borrow(),
); );
let next_index = pane_grid.next_selectable_pane_id_above(&active_pane_id); let next_index = pane_grid
.next_selectable_pane_id_above(&active_pane_id)
.or_else(|| pane_grid.progress_stack_up_if_in_stack(&active_pane_id));
if let Some(p) = next_index { if let Some(p) = next_index {
let active_pane_id = self.active_panes.get(&client_id).unwrap(); let active_pane_id = self.active_panes.get(&client_id).unwrap();
let current_position = self.panes.get(active_pane_id).unwrap(); let current_position = self.panes.get(active_pane_id).unwrap();
@ -1033,11 +1294,21 @@ impl TiledPanes {
.map(|(pane_id, _pane)| **pane_id); .map(|(pane_id, _pane)| **pane_id);
match next_active_pane_id { match next_active_pane_id {
Some(next_active_pane) => { Some(next_active_pane_id) => {
if self
.panes
.get(&next_active_pane_id)
.map(|p| p.current_geom().is_stacked)
.unwrap_or(false)
{
let _ = StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.focus_pane(&next_active_pane_id);
self.reapply_pane_frames();
}
for (client_id, active_pane_id) in active_panes { for (client_id, active_pane_id) in active_panes {
if active_pane_id == pane_id { if active_pane_id == pane_id {
self.active_panes self.active_panes
.insert(client_id, next_active_pane, &mut self.panes); .insert(client_id, next_active_pane_id, &mut self.panes);
} }
} }
}, },
@ -1160,13 +1431,13 @@ impl TiledPanes {
viewport_pane.set_geom_override(viewport_pane.position_and_size()); viewport_pane.set_geom_override(viewport_pane.position_and_size());
} }
let viewport = { *self.viewport.borrow() }; let viewport = { *self.viewport.borrow() };
let active_terminal = self.get_pane_mut(active_pane_id).unwrap(); let active_pane = self.get_pane_mut(active_pane_id).unwrap();
let full_screen_geom = PaneGeom { let full_screen_geom = PaneGeom {
x: viewport.x, x: viewport.x,
y: viewport.y, y: viewport.y,
..Default::default() ..Default::default()
}; };
active_terminal.set_geom_override(full_screen_geom); active_pane.set_geom_override(full_screen_geom);
} }
let connected_client_list: Vec<ClientId> = let connected_client_list: Vec<ClientId> =
{ self.connected_clients.borrow().iter().copied().collect() }; { self.connected_clients.borrow().iter().copied().collect() };
@ -1196,6 +1467,9 @@ impl TiledPanes {
pub fn panes_to_hide_count(&self) -> usize { pub fn panes_to_hide_count(&self) -> usize {
self.panes_to_hide.len() self.panes_to_hide.len()
} }
pub fn visible_panes_count(&self) -> usize {
self.panes.len().saturating_sub(self.panes_to_hide.len())
}
pub fn add_to_hidden_panels(&mut self, pid: PaneId) { pub fn add_to_hidden_panels(&mut self, pid: PaneId) {
self.panes_to_hide.insert(pid); self.panes_to_hide.insert(pid);
} }
@ -1208,6 +1482,18 @@ impl TiledPanes {
pub fn focus_all_panes(&mut self) { pub fn focus_all_panes(&mut self) {
self.active_panes.focus_all_panes(&mut self.panes); self.active_panes.focus_all_panes(&mut self.panes);
} }
pub fn drain(&mut self) -> BTreeMap<PaneId, Box<dyn Pane>> {
match self.panes.iter().next().map(|(pid, _p)| *pid) {
Some(first_pid) => self.panes.split_off(&first_pid),
None => BTreeMap::new(),
}
}
pub fn active_panes(&self) -> ActivePanes {
self.active_panes.clone()
}
pub fn set_active_panes(&mut self, active_panes: ActivePanes) {
self.active_panes = active_panes;
}
fn move_clients_between_panes(&mut self, from_pane_id: PaneId, to_pane_id: PaneId) { fn move_clients_between_panes(&mut self, from_pane_id: PaneId, to_pane_id: PaneId) {
let clients_in_pane: Vec<ClientId> = self let clients_in_pane: Vec<ClientId> = self
.active_panes .active_panes

View file

@ -1,3 +1,4 @@
use super::stacked_panes::StackedPanes;
use crate::{panes::PaneId, tab::Pane}; use crate::{panes::PaneId, tab::Pane};
use cassowary::{ use cassowary::{
strength::{REQUIRED, STRONG}, strength::{REQUIRED, STRONG},
@ -35,8 +36,8 @@ type Grid = Vec<Vec<Span>>;
impl<'a> PaneResizer<'a> { impl<'a> PaneResizer<'a> {
pub fn new(panes: Rc<RefCell<HashMap<PaneId, &'a mut Box<dyn Pane>>>>) -> Self { pub fn new(panes: Rc<RefCell<HashMap<PaneId, &'a mut Box<dyn Pane>>>>) -> Self {
let mut vars = HashMap::new(); let mut vars = HashMap::new();
for &k in panes.borrow().keys() { for &pane_id in panes.borrow().keys() {
vars.insert(k, Variable::new()); vars.insert(pane_id, Variable::new());
} }
PaneResizer { PaneResizer {
panes, panes,
@ -129,32 +130,64 @@ impl<'a> PaneResizer<'a> {
fn apply_spans(&mut self, spans: Vec<Span>) -> Result<()> { fn apply_spans(&mut self, spans: Vec<Span>) -> Result<()> {
let err_context = || format!("Failed to apply spans"); let err_context = || format!("Failed to apply spans");
let mut panes = self.panes.borrow_mut();
let mut geoms_changed = false; let mut geoms_changed = false;
for span in spans { for span in spans {
let pane = panes.get_mut(&span.pid).unwrap(); let pane_is_stacked = self
let current_geom = pane.position_and_size(); .panes
let new_geom = match span.direction { .borrow()
SplitDirection::Horizontal => PaneGeom { .get(&span.pid)
x: span.pos, .unwrap()
cols: span.size, .current_geom()
..pane.current_geom() .is_stacked;
}, if pane_is_stacked {
SplitDirection::Vertical => PaneGeom { let current_geom = StackedPanes::new(self.panes.clone())
y: span.pos, .position_and_size_of_stack(&span.pid)
rows: span.size, .unwrap();
..pane.current_geom() let new_geom = match span.direction {
}, SplitDirection::Horizontal => PaneGeom {
}; x: span.pos,
if new_geom.rows.as_usize() != current_geom.rows.as_usize() cols: span.size,
|| new_geom.cols.as_usize() != current_geom.cols.as_usize() ..current_geom
{ },
geoms_changed = true; SplitDirection::Vertical => PaneGeom {
} y: span.pos,
if pane.geom_override().is_some() { rows: span.size,
pane.set_geom_override(new_geom); ..current_geom
},
};
StackedPanes::new(self.panes.clone()).resize_panes_in_stack(&span.pid, new_geom)?;
// TODO: test with geom_override (fullscreen)
if new_geom.rows.as_usize() != current_geom.rows.as_usize()
|| new_geom.cols.as_usize() != current_geom.cols.as_usize()
{
geoms_changed = true;
}
} else { } else {
pane.set_geom(new_geom); let mut panes = self.panes.borrow_mut();
let pane = panes.get_mut(&span.pid).unwrap();
let current_geom = pane.position_and_size();
let new_geom = match span.direction {
SplitDirection::Horizontal => PaneGeom {
x: span.pos,
cols: span.size,
..pane.current_geom()
},
SplitDirection::Vertical => PaneGeom {
y: span.pos,
rows: span.size,
..pane.current_geom()
},
};
if new_geom.rows.as_usize() != current_geom.rows.as_usize()
|| new_geom.cols.as_usize() != current_geom.cols.as_usize()
{
geoms_changed = true;
}
if pane.geom_override().is_some() {
pane.set_geom_override(new_geom);
} else {
pane.set_geom(new_geom);
}
} }
} }
if geoms_changed { if geoms_changed {
@ -175,7 +208,7 @@ impl<'a> PaneResizer<'a> {
.panes .panes
.borrow() .borrow()
.values() .values()
.map(|p| self.get_span(!direction, p.as_ref())) .filter_map(|p| self.get_span(!direction, p.as_ref()))
.collect(); .collect();
let mut last_edge = 0; let mut last_edge = 0;
@ -197,38 +230,52 @@ impl<'a> PaneResizer<'a> {
.panes .panes
.borrow() .borrow()
.values() .values()
.filter(|p| { .filter(|p| match self.get_span(!direction, p.as_ref()) {
let s = self.get_span(!direction, p.as_ref()); Some(s) => {
let span_bounds = (s.pos, s.pos + s.size.as_usize()); let span_bounds = (s.pos, s.pos + s.size.as_usize());
bwn(span_bounds.0, boundary) bwn(span_bounds.0, boundary)
|| (bwn(boundary.0, span_bounds) || (bwn(boundary.0, span_bounds)
&& (bwn(boundary.1, span_bounds) || boundary.1 == span_bounds.1)) && (bwn(boundary.1, span_bounds) || boundary.1 == span_bounds.1))
},
None => false,
}) })
.map(|p| self.get_span(direction, p.as_ref())) .filter_map(|p| self.get_span(direction, p.as_ref()))
.collect(); .collect();
spans.sort_unstable_by_key(|s| s.pos); spans.sort_unstable_by_key(|s| s.pos);
spans spans
} }
fn get_span(&self, direction: SplitDirection, pane: &dyn Pane) -> Span { fn get_span(&self, direction: SplitDirection, pane: &dyn Pane) -> Option<Span> {
let pas = pane.current_geom(); let position_and_size = {
// let size_var = self.vars[&pane.pid()]; let pas = pane.current_geom();
if pas.is_stacked && pas.rows.is_percent() {
// this is the main pane of the stack
StackedPanes::new(self.panes.clone()).position_and_size_of_stack(&pane.pid())
} else if pas.is_stacked {
// this is a one-liner stacked pane and should be handled as the same rect with
// the rest of the stack, represented by the main pane in the if branch above
None
} else {
// non-stacked pane, treat normally
Some(pas)
}
}?;
let size_var = *self.vars.get(&pane.pid()).unwrap(); let size_var = *self.vars.get(&pane.pid()).unwrap();
match direction { match direction {
SplitDirection::Horizontal => Span { SplitDirection::Horizontal => Some(Span {
pid: pane.pid(), pid: pane.pid(),
direction, direction,
pos: pas.x, pos: position_and_size.x,
size: pas.cols, size: position_and_size.cols,
size_var, size_var,
}, }),
SplitDirection::Vertical => Span { SplitDirection::Vertical => Some(Span {
pid: pane.pid(), pid: pane.pid(),
direction, direction,
pos: pas.y, pos: position_and_size.y,
size: pas.rows, size: position_and_size.rows,
size_var, size_var,
}, }),
} }
} }
} }
@ -249,7 +296,7 @@ fn constrain_spans(space: usize, spans: &[Span]) -> HashSet<cassowary::Constrain
let full_size = spans let full_size = spans
.iter() .iter()
.fold(Expression::from_constant(0.0), |acc, s| acc + s.size_var); .fold(Expression::from_constant(0.0), |acc, s| acc + s.size_var);
constraints.insert(full_size | EQ(REQUIRED) | space as f64); constraints.insert(full_size.clone() | EQ(REQUIRED) | space as f64);
// Try to maintain ratios and lock non-flexible sizes // Try to maintain ratios and lock non-flexible sizes
for span in spans { for span in spans {

View file

@ -0,0 +1,630 @@
use crate::{panes::PaneId, tab::Pane};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use zellij_utils::{
errors::prelude::*,
pane_size::{Dimension, PaneGeom},
};
pub struct StackedPanes<'a> {
panes: Rc<RefCell<HashMap<PaneId, &'a mut Box<dyn Pane>>>>,
}
impl<'a> StackedPanes<'a> {
pub fn new(panes: Rc<RefCell<HashMap<PaneId, &'a mut Box<dyn Pane>>>>) -> Self {
StackedPanes { panes }
}
pub fn new_from_btreemap(
panes: impl IntoIterator<Item = (&'a PaneId, &'a mut Box<dyn Pane>)>,
panes_to_hide: &HashSet<PaneId>,
) -> Self {
let panes: HashMap<_, _> = panes
.into_iter()
.filter(|(p_id, _)| !panes_to_hide.contains(p_id))
.map(|(p_id, p)| (*p_id, p))
.collect();
let panes = Rc::new(RefCell::new(panes));
StackedPanes { panes }
}
pub fn move_down(
&mut self,
source_pane_id: &PaneId,
destination_pane_id: &PaneId,
) -> Result<()> {
let err_context = || format!("Failed to move stacked pane focus down");
let source_pane_is_stacked = self
.panes
.borrow()
.get(source_pane_id)
.with_context(err_context)?
.position_and_size()
.is_stacked;
let destination_pane_is_stacked = self
.panes
.borrow()
.get(destination_pane_id)
.with_context(err_context)?
.position_and_size()
.is_stacked;
if source_pane_is_stacked && destination_pane_is_stacked {
let mut panes = self.panes.borrow_mut();
let source_pane = panes.get_mut(source_pane_id).with_context(err_context)?;
let mut source_pane_geom = source_pane.position_and_size();
let mut destination_pane_geom = source_pane_geom.clone();
destination_pane_geom.y = source_pane_geom.y + 1;
source_pane_geom.rows = Dimension::fixed(1);
source_pane.set_geom(source_pane_geom);
let destination_pane = panes
.get_mut(&destination_pane_id)
.with_context(err_context)?;
destination_pane.set_geom(destination_pane_geom);
} else if destination_pane_is_stacked {
// we're moving down to the highest pane in the stack, we need to expand it and shrink the
// expanded stack pane
self.make_highest_pane_in_stack_flexible(destination_pane_id)?;
}
Ok(())
}
pub fn move_up(&mut self, source_pane_id: &PaneId, destination_pane_id: &PaneId) -> Result<()> {
let err_context = || format!("Failed to move stacked pane focus up");
let source_pane_is_stacked = self
.panes
.borrow()
.get(source_pane_id)
.with_context(err_context)?
.position_and_size()
.is_stacked;
let destination_pane_is_stacked = self
.panes
.borrow()
.get(destination_pane_id)
.with_context(err_context)?
.position_and_size()
.is_stacked;
if source_pane_is_stacked && destination_pane_is_stacked {
let mut panes = self.panes.borrow_mut();
let source_pane = panes.get_mut(source_pane_id).with_context(err_context)?;
let mut source_pane_geom = source_pane.position_and_size();
let mut destination_pane_geom = source_pane_geom.clone();
source_pane_geom.y = (source_pane_geom.y + source_pane_geom.rows.as_usize()) - 1; // -1 because we want to be at the last line of the source pane, not the next line over
source_pane_geom.rows = Dimension::fixed(1);
source_pane.set_geom(source_pane_geom);
destination_pane_geom.y -= 1;
let destination_pane = panes
.get_mut(&destination_pane_id)
.with_context(err_context)?;
destination_pane.set_geom(destination_pane_geom);
} else if destination_pane_is_stacked {
// we're moving up to the lowest pane in the stack, we need to expand it and shrink the
// expanded stack pane
self.make_lowest_pane_in_stack_flexible(destination_pane_id)?;
}
Ok(())
}
pub fn focus_pane(&mut self, pane_id: &PaneId) -> Result<()> {
// this function doesn't actually change the focus (since it is controlled elsewhere)
// but rather makes sure pane_id is flexible if it were a one-liner before
let err_context = || format!("Failed to focus stacked pane");
let all_stacked_pane_positions =
self.positions_in_stack(pane_id).with_context(err_context)?;
let position_of_flexible_pane = self
.position_of_flexible_pane(&all_stacked_pane_positions)
.with_context(err_context)?;
let (flexible_pane_id, mut flexible_pane) = *all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.with_context(err_context)?;
if flexible_pane_id != *pane_id {
let mut panes = self.panes.borrow_mut();
let height_of_flexible_pane = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.map(|(_pid, p)| p.rows)
.with_context(err_context)?;
let position_of_pane_to_focus = all_stacked_pane_positions
.iter()
.position(|(pid, _p)| pid == pane_id)
.with_context(err_context)?;
let (_, mut pane_to_focus) = *all_stacked_pane_positions
.iter()
.nth(position_of_pane_to_focus)
.with_context(err_context)?;
pane_to_focus.rows = height_of_flexible_pane;
panes
.get_mut(pane_id)
.with_context(err_context)?
.set_geom(pane_to_focus);
flexible_pane.rows = Dimension::fixed(1);
panes
.get_mut(&flexible_pane_id)
.with_context(err_context)?
.set_geom(flexible_pane);
for (i, (pid, _position)) in all_stacked_pane_positions.iter().enumerate() {
if i > position_of_pane_to_focus && i <= position_of_flexible_pane {
// the flexible pane has moved up the stack, we need to push this pane down
let pane = panes.get_mut(pid).with_context(err_context)?;
let mut pane_position_and_size = pane.position_and_size();
pane_position_and_size.y += height_of_flexible_pane.as_usize() - 1;
pane.set_geom(pane_position_and_size);
} else if i > position_of_flexible_pane && i <= position_of_pane_to_focus {
// the flexible pane has moved down the stack, we need to pull this pane up
let pane = panes.get_mut(pid).with_context(err_context)?;
let mut pane_position_and_size = pane.position_and_size();
pane_position_and_size.y -= height_of_flexible_pane.as_usize() - 1;
pane.set_geom(pane_position_and_size);
}
}
}
Ok(())
}
pub fn flexible_pane_id_in_stack(&self, pane_id_in_stack: &PaneId) -> Option<PaneId> {
let all_stacked_pane_positions = self.positions_in_stack(pane_id_in_stack).ok()?;
all_stacked_pane_positions
.iter()
.find(|(_pid, p)| p.rows.is_percent())
.map(|(pid, _p)| *pid)
}
pub fn position_and_size_of_stack(&self, id: &PaneId) -> Option<PaneGeom> {
let all_stacked_pane_positions = self.positions_in_stack(id).ok()?;
let position_of_flexible_pane = self
.position_of_flexible_pane(&all_stacked_pane_positions)
.ok()?;
let (_flexible_pane_id, flexible_pane) = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)?;
let (_, first_pane_in_stack) = all_stacked_pane_positions.first()?;
let (_, last_pane_in_stack) = all_stacked_pane_positions.last()?;
let mut rows = flexible_pane.rows;
rows.set_inner(
(last_pane_in_stack.y - first_pane_in_stack.y) + last_pane_in_stack.rows.as_usize(),
);
Some(PaneGeom {
y: first_pane_in_stack.y,
x: first_pane_in_stack.x,
cols: first_pane_in_stack.cols,
rows,
is_stacked: true, // important because otherwise the minimum stack size will not be
// respected
..Default::default()
})
}
pub fn increase_stack_width(&mut self, id: &PaneId, percent: f64) -> Result<()> {
let err_context = || format!("Failed to resize panes in stack");
let all_stacked_pane_positions = self.positions_in_stack(id).with_context(err_context)?;
for (pane_id, _pane_position) in all_stacked_pane_positions {
self.panes
.borrow_mut()
.get_mut(&pane_id)
.with_context(err_context)?
.increase_width(percent);
}
Ok(())
}
pub fn reduce_stack_width(&mut self, id: &PaneId, percent: f64) -> Result<()> {
let err_context = || format!("Failed to resize panes in stack");
let all_stacked_pane_positions = self.positions_in_stack(id).with_context(err_context)?;
for (pane_id, _pane_position) in all_stacked_pane_positions {
self.panes
.borrow_mut()
.get_mut(&pane_id)
.with_context(err_context)?
.reduce_width(percent);
}
Ok(())
}
pub fn increase_stack_height(&mut self, id: &PaneId, percent: f64) -> Result<()> {
let err_context = || format!("Failed to increase_stack_height");
let all_stacked_pane_positions = self.positions_in_stack(id).with_context(err_context)?;
let position_of_flexible_pane = self
.position_of_flexible_pane(&all_stacked_pane_positions)
.with_context(err_context)?;
let (flexible_pane_id, _flexible_pane) = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.with_context(err_context)?;
self.panes
.borrow_mut()
.get_mut(flexible_pane_id)
.with_context(err_context)?
.increase_height(percent);
Ok(())
}
pub fn reduce_stack_height(&mut self, id: &PaneId, percent: f64) -> Result<()> {
let err_context = || format!("Failed to increase_stack_height");
let all_stacked_pane_positions = self.positions_in_stack(id).with_context(err_context)?;
let position_of_flexible_pane = self
.position_of_flexible_pane(&all_stacked_pane_positions)
.with_context(err_context)?;
let (flexible_pane_id, _flexible_pane) = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.with_context(err_context)?;
self.panes
.borrow_mut()
.get_mut(flexible_pane_id)
.with_context(err_context)?
.reduce_height(percent);
Ok(())
}
pub fn min_stack_height(&mut self, id: &PaneId) -> Result<usize> {
let err_context = || format!("Failed to increase_stack_height");
let all_stacked_pane_positions = self.positions_in_stack(id).with_context(err_context)?;
Ok(all_stacked_pane_positions.len())
}
pub fn resize_panes_in_stack(
&mut self,
id: &PaneId,
new_full_stack_geom: PaneGeom,
) -> Result<()> {
let err_context = || format!("Failed to resize panes in stack");
let all_stacked_pane_positions = self.positions_in_stack(id).with_context(err_context)?;
let position_of_flexible_pane =
self.position_of_flexible_pane(&all_stacked_pane_positions)?;
let (flexible_pane_id, flexible_pane) = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.with_context(err_context)?;
let current_rows = all_stacked_pane_positions.len() + (flexible_pane.rows.as_usize() - 1);
let new_rows = new_full_stack_geom.rows.as_usize();
let adjust_stack_geoms = |new_flexible_pane_geom: PaneGeom| -> Result<()> {
let new_flexible_pane_geom_rows = new_flexible_pane_geom.rows.as_usize();
for (i, (pane_id, pane_geom)) in all_stacked_pane_positions.iter().enumerate() {
let mut new_pane_geom = if i == position_of_flexible_pane {
new_flexible_pane_geom
} else {
*pane_geom
};
new_pane_geom.x = new_full_stack_geom.x;
new_pane_geom.cols = new_full_stack_geom.cols;
if i <= position_of_flexible_pane {
new_pane_geom.y = new_full_stack_geom.y + i;
} else {
new_pane_geom.y = new_full_stack_geom.y + i + (new_flexible_pane_geom_rows - 1);
}
self.panes
.borrow_mut()
.get_mut(&pane_id)
.with_context(err_context)?
.set_geom(new_pane_geom);
}
Ok(())
};
if new_rows >= current_rows {
let extra_rows = new_rows - current_rows;
let mut new_flexible_pane_geom = *flexible_pane;
new_flexible_pane_geom
.rows
.set_inner(new_flexible_pane_geom.rows.as_usize() + extra_rows);
self.panes
.borrow_mut()
.get_mut(&flexible_pane_id)
.with_context(err_context)?
.set_geom(new_flexible_pane_geom);
adjust_stack_geoms(new_flexible_pane_geom)?;
} else {
if new_rows < all_stacked_pane_positions.len() {
// TODO: test this!! we don't want crashes...
return Err(anyhow!("Not enough room for stacked panes"));
}
let rows_deficit = current_rows - new_rows;
let mut new_flexible_pane_geom = *flexible_pane;
new_flexible_pane_geom
.rows
.set_inner(new_flexible_pane_geom.rows.as_usize() - rows_deficit);
self.panes
.borrow_mut()
.get_mut(&flexible_pane_id)
.with_context(err_context)?
.set_geom(new_flexible_pane_geom);
adjust_stack_geoms(new_flexible_pane_geom)?;
}
Ok(())
}
fn pane_is_one_liner(&self, id: &PaneId) -> Result<bool> {
let err_context = || format!("Cannot determin if pane is one liner or not");
let panes = self.panes.borrow();
let pane_to_close = panes.get(id).with_context(err_context)?;
Ok(pane_to_close.position_and_size().rows.is_fixed())
}
fn positions_in_stack(&self, id: &PaneId) -> Result<Vec<(PaneId, PaneGeom)>> {
// find the full stack of panes around the given id, sorted by pane location top to bottom
let err_context = || format!("Failed to find stacked panes");
let panes = self.panes.borrow();
let pane_in_stack = panes.get(id).with_context(err_context)?;
let mut all_stacked_pane_positions: Vec<(PaneId, PaneGeom)> = panes
.iter()
.filter(|(_pid, p)| p.position_and_size().is_stacked)
.filter(|(_pid, p)| {
p.position_and_size().x == pane_in_stack.position_and_size().x
&& p.position_and_size().cols == pane_in_stack.position_and_size().cols
})
.map(|(pid, p)| (*pid, p.position_and_size()))
.collect();
all_stacked_pane_positions.sort_by(|(_a_pid, a), (_b_pid, b)| a.y.cmp(&b.y));
Ok(all_stacked_pane_positions)
}
fn position_of_current_and_flexible_pane(
&self,
current_pane_id: &PaneId,
) -> Result<(usize, usize)> {
// (current_pane, flexible_pane)
let err_context = || format!("Failed to position_of_current_and_flexible_pane");
let all_stacked_pane_positions = self.positions_in_stack(current_pane_id)?;
let panes = self.panes.borrow();
let pane_to_close = panes.get(current_pane_id).with_context(err_context)?;
let position_of_current_pane =
self.position_of_current_pane(&all_stacked_pane_positions, &pane_to_close)?;
let position_of_flexible_pane =
self.position_of_flexible_pane(&all_stacked_pane_positions)?;
Ok((position_of_current_pane, position_of_flexible_pane))
}
fn position_of_current_pane(
&self,
all_stacked_pane_positions: &Vec<(PaneId, PaneGeom)>,
pane_to_close: &Box<dyn Pane>,
) -> Result<usize> {
let err_context = || format!("Failed to find position of current pane");
all_stacked_pane_positions
.iter()
.position(|(pid, _p)| pid == &pane_to_close.pid())
.with_context(err_context)
}
fn position_of_flexible_pane(
&self,
all_stacked_pane_positions: &Vec<(PaneId, PaneGeom)>,
) -> Result<usize> {
let err_context = || format!("Failed to find position of flexible pane");
all_stacked_pane_positions
.iter()
.position(|(_pid, p)| p.rows.is_percent())
.with_context(err_context)
}
pub fn fill_space_over_pane_in_stack(&mut self, id: &PaneId) -> Result<bool> {
if self.pane_is_one_liner(id)? {
self.fill_space_over_one_liner_pane(id)
} else {
self.fill_space_over_visible_stacked_pane(id)
}
}
pub fn stacked_pane_ids_under_and_over_flexible_panes(
&self,
) -> Result<(HashSet<PaneId>, HashSet<PaneId>)> {
let mut stacked_pane_ids_under_flexible_panes = HashSet::new();
let mut stacked_pane_ids_over_flexible_panes = HashSet::new();
let mut seen = HashSet::new();
let pane_ids_in_stacks: Vec<PaneId> = {
self.panes
.borrow()
.iter()
.filter(|(_p_id, p)| p.position_and_size().is_stacked)
.map(|(p_id, _p)| *p_id)
.collect()
};
for pane_id in pane_ids_in_stacks {
if !seen.contains(&pane_id) {
let mut current_pane_is_above_stack = true;
let positions_in_stack = self.positions_in_stack(&pane_id)?;
for (pane_id, pane_geom) in positions_in_stack {
seen.insert(pane_id);
if pane_geom.rows.is_percent() {
// this is the flexible pane
current_pane_is_above_stack = false;
continue;
}
if current_pane_is_above_stack {
stacked_pane_ids_over_flexible_panes.insert(pane_id);
} else {
stacked_pane_ids_under_flexible_panes.insert(pane_id);
}
}
seen.insert(pane_id);
}
}
Ok((
stacked_pane_ids_under_flexible_panes,
stacked_pane_ids_over_flexible_panes,
))
}
fn fill_space_over_one_liner_pane(&mut self, id: &PaneId) -> Result<bool> {
let (position_of_current_pane, position_of_flexible_pane) =
self.position_of_current_and_flexible_pane(id)?;
if position_of_current_pane > position_of_flexible_pane {
self.fill_space_over_one_liner_pane_above_flexible_pane(id)
} else {
self.fill_space_over_one_liner_pane_below_flexible_pane(id)
}
}
fn fill_space_over_visible_stacked_pane(&mut self, id: &PaneId) -> Result<bool> {
let err_context = || format!("Failed to fill_space_over_visible_stacked_pane");
let all_stacked_pane_positions = self.positions_in_stack(id)?;
let mut panes = self.panes.borrow_mut();
let pane_to_close = panes.get(id).with_context(err_context)?;
let position_of_current_pane =
self.position_of_current_pane(&all_stacked_pane_positions, &pane_to_close)?;
if all_stacked_pane_positions.len() > position_of_current_pane + 1 {
let mut pane_to_close_position_and_size = pane_to_close.position_and_size();
pane_to_close_position_and_size
.rows
.set_inner(pane_to_close_position_and_size.rows.as_usize() + 1);
let pane_id_below = all_stacked_pane_positions
.iter()
.nth(position_of_current_pane + 1)
.map(|(pid, _)| *pid)
.with_context(err_context)?;
let pane_below = panes.get_mut(&pane_id_below).with_context(err_context)?;
pane_below.set_geom(pane_to_close_position_and_size);
return Ok(true);
} else if position_of_current_pane > 0 {
let mut pane_to_close_position_and_size = pane_to_close.position_and_size();
pane_to_close_position_and_size
.rows
.set_inner(pane_to_close_position_and_size.rows.as_usize() + 1);
pane_to_close_position_and_size.y -= 1;
let pane_id_above = all_stacked_pane_positions
.iter()
.nth(position_of_current_pane - 1)
.map(|(pid, _)| *pid)
.with_context(err_context)?;
let pane_above = panes.get_mut(&pane_id_above).with_context(err_context)?;
pane_above.set_geom(pane_to_close_position_and_size);
return Ok(true);
} else {
return Ok(false);
}
}
fn fill_space_over_one_liner_pane_above_flexible_pane(&mut self, id: &PaneId) -> Result<bool> {
let err_context =
|| format!("Failed to fill_space_over_one_liner_pane_above_flexible_pane");
let all_stacked_pane_positions = self.positions_in_stack(id)?;
let mut panes = self.panes.borrow_mut();
let pane_to_close = panes.get(id).with_context(err_context)?;
let position_of_current_pane =
self.position_of_current_pane(&all_stacked_pane_positions, &pane_to_close)?;
let position_of_flexible_pane =
self.position_of_flexible_pane(&all_stacked_pane_positions)?;
let id_of_flexible_pane = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.map(|(pid, _p)| *pid)
.with_context(err_context)?;
let flexible_pane = panes
.get_mut(&id_of_flexible_pane)
.with_context(err_context)?;
let mut flexible_pane_position_and_size = flexible_pane.position_and_size();
flexible_pane_position_and_size
.rows
.set_inner(flexible_pane_position_and_size.rows.as_usize() + 1);
flexible_pane.set_geom(flexible_pane_position_and_size);
for (i, (pid, _position)) in all_stacked_pane_positions.iter().enumerate() {
if i > position_of_flexible_pane && i < position_of_current_pane {
let pane = panes.get_mut(pid).with_context(err_context)?;
let mut pane_position_and_size = pane.position_and_size();
pane_position_and_size.y += 1;
pane.set_geom(pane_position_and_size);
}
}
Ok(true)
}
fn fill_space_over_one_liner_pane_below_flexible_pane(&mut self, id: &PaneId) -> Result<bool> {
let err_context =
|| format!("Failed to fill_space_over_one_liner_pane_below_flexible_pane");
let all_stacked_pane_positions = self.positions_in_stack(id)?;
let mut panes = self.panes.borrow_mut();
let pane_to_close = panes.get(id).with_context(err_context)?;
let position_of_current_pane =
self.position_of_current_pane(&all_stacked_pane_positions, &pane_to_close)?;
let position_of_flexible_pane =
self.position_of_flexible_pane(&all_stacked_pane_positions)?;
let id_of_flexible_pane = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.map(|(pid, _p)| *pid)
.with_context(err_context)?;
let flexible_pane = panes
.get_mut(&id_of_flexible_pane)
.with_context(err_context)?;
let mut flexible_pane_position_and_size = flexible_pane.position_and_size();
flexible_pane_position_and_size
.rows
.set_inner(flexible_pane_position_and_size.rows.as_usize() + 1);
flexible_pane.set_geom(flexible_pane_position_and_size);
for (i, (pid, _position)) in all_stacked_pane_positions.iter().enumerate() {
if i > position_of_current_pane && i <= position_of_flexible_pane {
let pane = panes.get_mut(pid).with_context(err_context)?;
let mut pane_position_and_size = pane.position_and_size();
pane_position_and_size.y = pane_position_and_size.y.saturating_sub(1);
pane.set_geom(pane_position_and_size);
}
}
Ok(true)
}
fn make_lowest_pane_in_stack_flexible(&mut self, destination_pane_id: &PaneId) -> Result<()> {
let err_context = || format!("Failed to make_lowest_pane_flexible");
let mut all_stacked_pane_positions = self.positions_in_stack(destination_pane_id)?;
let position_of_flexible_pane =
self.position_of_flexible_pane(&all_stacked_pane_positions)?;
if position_of_flexible_pane != all_stacked_pane_positions.len().saturating_sub(1) {
let mut panes = self.panes.borrow_mut();
let height_of_flexible_pane = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.map(|(_pid, p)| p.rows)
.with_context(err_context)?;
let (lowest_pane_id, mut lowest_pane_geom) = all_stacked_pane_positions
.last_mut()
.with_context(err_context)?;
lowest_pane_geom.rows = height_of_flexible_pane;
panes
.get_mut(lowest_pane_id)
.with_context(err_context)?
.set_geom(lowest_pane_geom);
let (flexible_pane_id, mut flexible_pane_geom) = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.with_context(err_context)?;
flexible_pane_geom.rows = Dimension::fixed(1);
panes
.get_mut(flexible_pane_id)
.with_context(err_context)?
.set_geom(flexible_pane_geom);
for (i, (pid, _position)) in all_stacked_pane_positions.iter().enumerate() {
if i > position_of_flexible_pane {
let pane = panes.get_mut(pid).with_context(err_context)?;
let mut pane_position_and_size = pane.position_and_size();
pane_position_and_size.y = pane_position_and_size
.y
.saturating_sub(height_of_flexible_pane.as_usize() - 1);
pane.set_geom(pane_position_and_size);
}
}
}
Ok(())
}
fn make_highest_pane_in_stack_flexible(&mut self, destination_pane_id: &PaneId) -> Result<()> {
let err_context = || format!("Failed to make_lowest_pane_flexible");
let mut all_stacked_pane_positions = self.positions_in_stack(destination_pane_id)?;
let position_of_flexible_pane =
self.position_of_flexible_pane(&all_stacked_pane_positions)?;
if position_of_flexible_pane != 0 {
let mut panes = self.panes.borrow_mut();
let height_of_flexible_pane = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.map(|(_pid, p)| p.rows)
.with_context(err_context)?;
let (highest_pane_id, mut highest_pane_geom) = all_stacked_pane_positions
.first_mut()
.with_context(err_context)?;
let y_of_whole_stack = highest_pane_geom.y;
highest_pane_geom.rows = height_of_flexible_pane;
panes
.get_mut(highest_pane_id)
.with_context(err_context)?
.set_geom(highest_pane_geom);
let (flexible_pane_id, mut flexible_pane_geom) = all_stacked_pane_positions
.iter()
.nth(position_of_flexible_pane)
.with_context(err_context)?;
flexible_pane_geom.rows = Dimension::fixed(1);
panes
.get_mut(flexible_pane_id)
.with_context(err_context)?
.set_geom(flexible_pane_geom);
for (i, (pid, _position)) in all_stacked_pane_positions.iter().enumerate() {
if i > 0 {
let pane = panes.get_mut(pid).with_context(err_context)?;
let mut pane_position_and_size = pane.position_and_size();
pane_position_and_size.y =
y_of_whole_stack + height_of_flexible_pane.as_usize() + (i - 1);
pane.set_geom(pane_position_and_size);
}
}
}
Ok(())
}
}

View file

@ -1,5 +1,6 @@
use super::is_inside_viewport; use super::is_inside_viewport;
use super::pane_resizer::PaneResizer; use super::pane_resizer::PaneResizer;
use super::stacked_panes::StackedPanes;
use crate::tab::{MIN_TERMINAL_HEIGHT, MIN_TERMINAL_WIDTH}; use crate::tab::{MIN_TERMINAL_HEIGHT, MIN_TERMINAL_WIDTH};
use crate::{panes::PaneId, tab::Pane}; use crate::{panes::PaneId, tab::Pane};
use std::cmp::Reverse; use std::cmp::Reverse;
@ -49,42 +50,32 @@ impl<'a> TiledPaneGrid<'a> {
} }
} }
/// Calculates an area for each pane and sums them all.
///
/// Returns the product of "rows * columns", summed across all panes.
#[cfg(debug_assertions)]
fn total_panes_area(&self) -> f64 {
let mut summed_area: f64 = 0.0;
for pane in self.panes.clone().borrow().values() {
let geom = pane.current_geom();
summed_area += match (geom.rows.as_percent(), geom.cols.as_percent()) {
(Some(rows), Some(cols)) => rows * cols,
_ => continue,
};
}
summed_area / (100.0 * 100.0)
}
pub fn layout(&mut self, direction: SplitDirection, space: usize) -> Result<()> { pub fn layout(&mut self, direction: SplitDirection, space: usize) -> Result<()> {
let mut pane_resizer = PaneResizer::new(self.panes.clone()); let mut pane_resizer = PaneResizer::new(self.panes.clone());
pane_resizer.layout(direction, space) pane_resizer.layout(direction, space)
} }
fn get_pane_geom(&self, pane_id: &PaneId) -> Option<PaneGeom> {
let panes = self.panes.borrow();
let pane_to_check = panes.get(pane_id)?;
let pane_geom = pane_to_check.current_geom();
if pane_geom.is_stacked {
StackedPanes::new(self.panes.clone()).position_and_size_of_stack(&pane_id)
} else {
Some(pane_geom)
}
}
fn pane_is_flexible(&self, direction: SplitDirection, pane_id: &PaneId) -> Result<bool> { fn pane_is_flexible(&self, direction: SplitDirection, pane_id: &PaneId) -> Result<bool> {
let err_context = let err_context =
|| format!("failed to determine if pane {pane_id:?} is flexible in {direction:?}"); || format!("failed to determine if pane {pane_id:?} is flexible in {direction:?}");
let panes = self.panes.borrow(); let pane_geom = self
let pane_to_check = panes .get_pane_geom(pane_id)
.get(pane_id)
.with_context(|| no_pane_id(pane_id)) .with_context(|| no_pane_id(pane_id))
.with_context(err_context)?; .with_context(err_context)?;
let geom = pane_to_check.current_geom();
Ok(!match direction { Ok(!match direction {
SplitDirection::Vertical => geom.rows, SplitDirection::Vertical => pane_geom.rows,
SplitDirection::Horizontal => geom.cols, SplitDirection::Horizontal => pane_geom.cols,
} }
.is_fixed()) .is_fixed())
} }
@ -99,12 +90,12 @@ impl<'a> TiledPaneGrid<'a> {
let neighbor_terminal_borders: HashSet<_> = if direction.is_horizontal() { let neighbor_terminal_borders: HashSet<_> = if direction.is_horizontal() {
neighbor_terminals neighbor_terminals
.iter() .iter()
.map(|t| self.panes.borrow().get(t).unwrap().y()) .filter_map(|t| self.get_pane_geom(t).map(|p| p.y))
.collect() .collect()
} else { } else {
neighbor_terminals neighbor_terminals
.iter() .iter()
.map(|t| self.panes.borrow().get(t).unwrap().x()) .filter_map(|t| self.get_pane_geom(t).map(|p| p.x))
.collect() .collect()
}; };
@ -300,12 +291,12 @@ impl<'a> TiledPaneGrid<'a> {
let neighbor_terminal_borders: HashSet<_> = if direction.is_horizontal() { let neighbor_terminal_borders: HashSet<_> = if direction.is_horizontal() {
neighbor_terminals neighbor_terminals
.iter() .iter()
.map(|t| self.panes.borrow().get(t).unwrap().y()) .filter_map(|p| self.get_pane_geom(p).map(|p| p.y))
.collect() .collect()
} else { } else {
neighbor_terminals neighbor_terminals
.iter() .iter()
.map(|t| self.panes.borrow().get(t).unwrap().x()) .filter_map(|p| self.get_pane_geom(p).map(|p| p.x))
.collect() .collect()
}; };
@ -408,34 +399,34 @@ impl<'a> TiledPaneGrid<'a> {
]; ];
// For the borrow checker // For the borrow checker
{ {
let panes = self.panes.borrow(); // let panes = self.panes.borrow();
let active_pane = panes let active_pane = self
.get(pane_id) .get_pane_geom(pane_id)
.with_context(|| no_pane_id(pane_id)) .with_context(|| no_pane_id(pane_id))
.with_context(err_context)?; .with_context(err_context)?;
for p_id in self.viewport_pane_ids_directly_below(pane_id) { for p_id in self.viewport_pane_ids_directly_below(pane_id) {
let pane = panes let pane = self
.get(&p_id) .get_pane_geom(&p_id)
.with_context(|| no_pane_id(&p_id)) .with_context(|| no_pane_id(&p_id))
.with_context(err_context)?; .with_context(err_context)?;
if active_pane.x() + active_pane.cols() == pane.x() { if active_pane.x + active_pane.cols.as_usize() == pane.x {
// right aligned // right aligned
aligned_panes[0] = Some(p_id); aligned_panes[0] = Some(p_id);
} else if active_pane.x() == pane.x() + pane.cols() { } else if active_pane.x == pane.x + pane.cols.as_usize() {
// left aligned // left aligned
aligned_panes[1] = Some(p_id); aligned_panes[1] = Some(p_id);
} }
} }
for p_id in self.viewport_pane_ids_directly_above(pane_id) { for p_id in self.viewport_pane_ids_directly_above(pane_id) {
let pane = panes let pane = self
.get(&p_id) .get_pane_geom(&p_id)
.with_context(|| no_pane_id(&p_id)) .with_context(|| no_pane_id(&p_id))
.with_context(err_context)?; .with_context(err_context)?;
if active_pane.x() + active_pane.cols() == pane.x() { if active_pane.x + active_pane.cols.as_usize() == pane.x {
// right aligned // right aligned
aligned_panes[2] = Some(p_id); aligned_panes[2] = Some(p_id);
} else if active_pane.x() == pane.x() + pane.cols() { } else if active_pane.x == pane.x + pane.cols.as_usize() {
// left aligned // left aligned
aligned_panes[3] = Some(p_id); aligned_panes[3] = Some(p_id);
} }
@ -467,8 +458,15 @@ impl<'a> TiledPaneGrid<'a> {
..strategy ..strategy
}; };
if self.can_change_pane_size(pane_id, &main_strategy, change_by)? // TODO: instead of unwrap_or(false) here we need to do the same with the fixed
&& self.can_change_pane_size(pane_id, &sub_strategy, change_by)? // panes error above, only make sure that we only error if we cannot resize in
// any directions and have blocking fixed panes
if self
.can_change_pane_size(pane_id, &main_strategy, change_by)
.unwrap_or(false)
&& self
.can_change_pane_size(pane_id, &sub_strategy, change_by)
.unwrap_or(false)
{ {
let result = self let result = self
.change_pane_size(pane_id, &main_strategy, change_by) .change_pane_size(pane_id, &main_strategy, change_by)
@ -512,16 +510,6 @@ impl<'a> TiledPaneGrid<'a> {
return Ok(false); return Ok(false);
} }
#[cfg(debug_assertions)]
{
let area = self.total_panes_area() * 100.0;
debug_assert!(
f64::abs(area - 100.0) < 1.0, // Tolerate a little rounding error
"area consumed by panes doesn't fill the viewport! Total area is {area} %
During operation: '{strategy}', on pane {pane_id:?}",
);
}
Ok(true) Ok(true)
} }
@ -529,16 +517,15 @@ impl<'a> TiledPaneGrid<'a> {
let err_context = let err_context =
|| format!("failed to determine if pane {pane_id:?} can reduce width by {reduce_by} %"); || format!("failed to determine if pane {pane_id:?} can reduce width by {reduce_by} %");
let panes = self.panes.borrow(); let pane = self
let pane = panes .get_pane_geom(pane_id)
.get(pane_id)
.with_context(|| no_pane_id(pane_id)) .with_context(|| no_pane_id(pane_id))
.with_context(err_context)?; .with_context(err_context)?;
let current_fixed_cols = pane.position_and_size().cols.as_usize(); let current_fixed_cols = pane.cols.as_usize();
let will_reduce_by = ((self.display_area.cols as f64 / 100.0) * reduce_by) as usize; let will_reduce_by = ((self.display_area.cols as f64 / 100.0) * reduce_by) as usize;
if current_fixed_cols.saturating_sub(will_reduce_by) < pane.min_width() { if current_fixed_cols.saturating_sub(will_reduce_by) < MIN_TERMINAL_WIDTH {
Ok(false) Ok(false)
} else if let Some(cols) = pane.position_and_size().cols.as_percent() { } else if let Some(cols) = pane.cols.as_percent() {
Ok(cols - reduce_by >= RESIZE_PERCENT) Ok(cols - reduce_by >= RESIZE_PERCENT)
} else { } else {
Ok(false) Ok(false)
@ -549,16 +536,20 @@ impl<'a> TiledPaneGrid<'a> {
format!("failed to determine if pane {pane_id:?} can reduce height by {reduce_by} %") format!("failed to determine if pane {pane_id:?} can reduce height by {reduce_by} %")
}; };
let panes = self.panes.borrow(); let pane = self
let pane = panes .get_pane_geom(pane_id)
.get(pane_id)
.with_context(|| no_pane_id(pane_id)) .with_context(|| no_pane_id(pane_id))
.with_context(err_context)?; .with_context(err_context)?;
let current_fixed_rows = pane.position_and_size().rows.as_usize(); let min_terminal_height = if pane.is_stacked {
StackedPanes::new(self.panes.clone()).min_stack_height(pane_id)?
} else {
MIN_TERMINAL_HEIGHT
};
let current_fixed_rows = pane.rows.as_usize();
let will_reduce_by = ((self.display_area.rows as f64 / 100.0) * reduce_by) as usize; let will_reduce_by = ((self.display_area.rows as f64 / 100.0) * reduce_by) as usize;
if current_fixed_rows.saturating_sub(will_reduce_by) < pane.min_height() { if current_fixed_rows.saturating_sub(will_reduce_by) < min_terminal_height {
Ok(false) Ok(false)
} else if let Some(rows) = pane.position_and_size().rows.as_percent() { } else if let Some(rows) = pane.rows.as_percent() {
Ok(rows - reduce_by >= RESIZE_PERCENT) Ok(rows - reduce_by >= RESIZE_PERCENT)
} else { } else {
Ok(false) Ok(false)
@ -567,26 +558,70 @@ impl<'a> TiledPaneGrid<'a> {
fn reduce_pane_height(&mut self, id: &PaneId, percent: f64) { fn reduce_pane_height(&mut self, id: &PaneId, percent: f64) {
if self.can_reduce_pane_height(id, percent).unwrap() { if self.can_reduce_pane_height(id, percent).unwrap() {
let mut panes = self.panes.borrow_mut(); let current_pane_is_stacked = self
let terminal = panes.get_mut(id).unwrap(); .panes
terminal.reduce_height(percent); .borrow()
.get(id)
.unwrap()
.current_geom()
.is_stacked;
if current_pane_is_stacked {
let _ = StackedPanes::new(self.panes.clone()).reduce_stack_height(&id, percent);
} else {
let mut panes = self.panes.borrow_mut();
let terminal = panes.get_mut(id).unwrap();
terminal.reduce_height(percent);
}
} }
} }
fn increase_pane_height(&mut self, id: &PaneId, percent: f64) { fn increase_pane_height(&mut self, id: &PaneId, percent: f64) {
let mut panes = self.panes.borrow_mut(); let current_pane_is_stacked = self
let terminal = panes.get_mut(id).unwrap(); .panes
terminal.increase_height(percent); .borrow()
.get(id)
.unwrap()
.current_geom()
.is_stacked;
if current_pane_is_stacked {
let _ = StackedPanes::new(self.panes.clone()).increase_stack_height(&id, percent);
} else {
let mut panes = self.panes.borrow_mut();
let terminal = panes.get_mut(id).unwrap();
terminal.increase_height(percent);
}
} }
fn increase_pane_width(&mut self, id: &PaneId, percent: f64) { fn increase_pane_width(&mut self, id: &PaneId, percent: f64) {
let mut panes = self.panes.borrow_mut(); let current_pane_is_stacked = self
let terminal = panes.get_mut(id).unwrap(); .panes
terminal.increase_width(percent); .borrow()
.get(id)
.unwrap()
.current_geom()
.is_stacked;
if current_pane_is_stacked {
let _ = StackedPanes::new(self.panes.clone()).increase_stack_width(&id, percent);
} else {
let mut panes = self.panes.borrow_mut();
let pane = panes.get_mut(id).unwrap();
pane.increase_width(percent);
}
} }
fn reduce_pane_width(&mut self, id: &PaneId, percent: f64) { fn reduce_pane_width(&mut self, id: &PaneId, percent: f64) {
if self.can_reduce_pane_width(id, percent).unwrap() { if self.can_reduce_pane_width(id, percent).unwrap() {
let mut panes = self.panes.borrow_mut(); let current_pane_is_stacked = self
let terminal = panes.get_mut(id).unwrap(); .panes
terminal.reduce_width(percent); .borrow()
.get(id)
.unwrap()
.current_geom()
.is_stacked;
if current_pane_is_stacked {
let _ = StackedPanes::new(self.panes.clone()).reduce_stack_width(&id, percent);
} else {
let mut panes = self.panes.borrow_mut();
let terminal = panes.get_mut(id).unwrap();
terminal.reduce_width(percent);
}
} }
} }
@ -597,27 +632,38 @@ impl<'a> TiledPaneGrid<'a> {
fn pane_ids_directly_next_to(&self, id: &PaneId, direction: &Direction) -> Result<Vec<PaneId>> { fn pane_ids_directly_next_to(&self, id: &PaneId, direction: &Direction) -> Result<Vec<PaneId>> {
let err_context = || format!("failed to find panes {direction} from pane {id:?}"); let err_context = || format!("failed to find panes {direction} from pane {id:?}");
let panes = self.panes.borrow();
let mut ids = vec![]; let mut ids = vec![];
let terminal_to_check = panes let pane_geom_to_check = self
.get(id) .get_pane_geom(id)
.with_context(|| no_pane_id(id)) .with_context(|| no_pane_id(id))
.with_context(err_context)?; .with_context(err_context)?;
for (&pid, terminal) in panes.iter() { let panes = self.panes.borrow();
let mut seen = HashSet::new();
for pid in panes.keys() {
let pane = self
.get_pane_geom(pid)
.with_context(|| no_pane_id(id))
.with_context(err_context)?;
if seen.contains(&pane) {
continue;
} else {
seen.insert(pane);
}
if match direction { if match direction {
Direction::Left => (terminal.x() + terminal.cols()) == terminal_to_check.x(), Direction::Left => (pane.x + pane.cols.as_usize()) == pane_geom_to_check.x,
Direction::Down => { Direction::Down => {
terminal.y() == (terminal_to_check.y() + terminal_to_check.rows()) pane.y == (pane_geom_to_check.y + pane_geom_to_check.rows.as_usize())
}, },
Direction::Up => (terminal.y() + terminal.rows()) == terminal_to_check.y(), Direction::Up => (pane.y + pane.rows.as_usize()) == pane_geom_to_check.y,
Direction::Right => { Direction::Right => {
terminal.x() == (terminal_to_check.x() + terminal_to_check.cols()) pane.x == (pane_geom_to_check.x + pane_geom_to_check.cols.as_usize())
}, },
} { } {
ids.push(pid); ids.push(*pid);
} }
} }
Ok(ids) Ok(ids)
} }
@ -629,29 +675,38 @@ impl<'a> TiledPaneGrid<'a> {
) -> Result<Vec<PaneId>> { ) -> Result<Vec<PaneId>> {
let err_context = || format!("failed to find panes aligned {direction} with {pane_id:?}"); let err_context = || format!("failed to find panes aligned {direction} with {pane_id:?}");
let panes = self.panes.borrow(); let pane_to_check = self
let pane_to_check = panes .get_pane_geom(pane_id)
.get(pane_id)
.with_context(|| no_pane_id(pane_id)) .with_context(|| no_pane_id(pane_id))
.with_context(err_context)?; .with_context(err_context)?;
let mut result = vec![]; let mut result = vec![];
for (p_id, pane) in panes.iter() { let panes = self.panes.borrow();
if p_id == pane_id { let mut seen = HashSet::new();
for (pid, _pane) in panes.iter() {
let pane = self
.get_pane_geom(pid)
.with_context(|| no_pane_id(pane_id))
.with_context(err_context)?;
if seen.contains(&pane) || pid == pane_id {
continue; continue;
} else {
seen.insert(pane);
} }
if match direction { if match direction {
Direction::Left => pane.x() == pane_to_check.x(), Direction::Left => pane.x == pane_to_check.x,
Direction::Down => { Direction::Down => {
(pane.y() + pane.rows()) == (pane_to_check.y() + pane_to_check.rows()) (pane.y + pane.rows.as_usize())
== (pane_to_check.y + pane_to_check.rows.as_usize())
}, },
Direction::Up => pane.y() == pane_to_check.y(), Direction::Up => pane.y == pane_to_check.y,
Direction::Right => { Direction::Right => {
(pane.x() + pane.cols()) == (pane_to_check.x() + pane_to_check.cols()) (pane.x + pane.cols.as_usize())
== (pane_to_check.x + pane_to_check.cols.as_usize())
}, },
} { } {
result.push(*p_id) result.push(*pid)
} }
} }
Ok(result) Ok(result)
@ -671,9 +726,8 @@ impl<'a> TiledPaneGrid<'a> {
let input_error = let input_error =
anyhow!("Invalid combination of alignment ({alignment}) and direction ({direction})"); anyhow!("Invalid combination of alignment ({alignment}) and direction ({direction})");
let panes = self.panes.borrow(); let pane_to_check = self
let pane_to_check = panes .get_pane_geom(id)
.get(id)
.with_context(|| no_pane_id(id)) .with_context(|| no_pane_id(id))
.with_context(err_context)?; .with_context(err_context)?;
let mut result = vec![]; let mut result = vec![];
@ -682,7 +736,7 @@ impl<'a> TiledPaneGrid<'a> {
.and_then(|pane_ids| { .and_then(|pane_ids| {
Ok(pane_ids Ok(pane_ids
.iter() .iter()
.map(|p_id| panes.get(p_id).unwrap()) // <-- TODO: Bad unwrap! .filter_map(|p_id| self.get_pane_geom(p_id).map(|pane_geom| (*p_id, pane_geom)))
.collect()) .collect())
}) })
.with_context(err_context)?; .with_context(err_context)?;
@ -693,23 +747,26 @@ impl<'a> TiledPaneGrid<'a> {
use Direction::Up as U; use Direction::Up as U;
match (alignment, direction) { match (alignment, direction) {
(&R, &U) | (&L, &U) => aligned_panes.sort_by_key(|a| Reverse(a.y())), (&R, &U) | (&L, &U) => aligned_panes.sort_by_key(|(_, a)| Reverse(a.y)),
(&R, &D) | (&L, &D) => aligned_panes.sort_by_key(|a| a.y()), (&R, &D) | (&L, &D) => aligned_panes.sort_by_key(|(_, a)| a.y),
(&D, &L) | (&U, &L) => aligned_panes.sort_by_key(|a| Reverse(a.x())), (&D, &L) | (&U, &L) => aligned_panes.sort_by_key(|(_, a)| Reverse(a.x)),
(&D, &R) | (&U, &R) => aligned_panes.sort_by_key(|a| a.x()), (&D, &R) | (&U, &R) => aligned_panes.sort_by_key(|(_, a)| a.x),
_ => return Err(input_error).with_context(err_context), _ => return Err(input_error).with_context(err_context),
}; };
for pane in aligned_panes { for (pid, pane) in aligned_panes {
let pane_to_check = result.last().unwrap_or(&pane_to_check); let pane_to_check = result
.last()
.map(|(_pid, pane)| pane)
.unwrap_or(&pane_to_check);
if match (alignment, direction) { if match (alignment, direction) {
(&R, &U) | (&L, &U) => (pane.y() + pane.rows()) == pane_to_check.y(), (&R, &U) | (&L, &U) => (pane.y + pane.rows.as_usize()) == pane_to_check.y,
(&R, &D) | (&L, &D) => pane.y() == (pane_to_check.y() + pane_to_check.rows()), (&R, &D) | (&L, &D) => pane.y == (pane_to_check.y + pane_to_check.rows.as_usize()),
(&D, &L) | (&U, &L) => (pane.x() + pane.cols()) == pane_to_check.x(), (&D, &L) | (&U, &L) => (pane.x + pane.cols.as_usize()) == pane_to_check.x,
(&D, &R) | (&U, &R) => pane.x() == (pane_to_check.x() + pane_to_check.cols()), (&D, &R) | (&U, &R) => pane.x == (pane_to_check.x + pane_to_check.cols.as_usize()),
_ => return Err(input_error).with_context(err_context), _ => return Err(input_error).with_context(err_context),
} { } {
result.push(pane); result.push((pid, pane));
} }
} }
@ -720,12 +777,12 @@ impl<'a> TiledPaneGrid<'a> {
&R => self.viewport.x + self.viewport.cols, &R => self.viewport.x + self.viewport.cols,
}; };
for pane in &result { for (_, pane) in &result {
let pane_boundary = match direction { let pane_boundary = match direction {
&L => pane.x() + pane.cols(), &L => pane.x + pane.cols.as_usize(),
&D => pane.y(), &D => pane.y,
&U => pane.y() + pane.rows(), &U => pane.y + pane.rows.as_usize(),
&R => pane.x(), &R => pane.x,
}; };
if border.get(&pane_boundary).is_some() { if border.get(&pane_boundary).is_some() {
match direction { match direction {
@ -742,24 +799,24 @@ impl<'a> TiledPaneGrid<'a> {
} }
} }
} }
result.retain(|pane| match direction { result.retain(|(_pid, pane)| match direction {
&L => pane.x() >= resize_border, &L => pane.x >= resize_border,
&D => (pane.y() + pane.rows()) <= resize_border, &D => (pane.y + pane.rows.as_usize()) <= resize_border,
&U => pane.y() >= resize_border, &U => pane.y >= resize_border,
&R => (pane.x() + pane.cols()) <= resize_border, &R => (pane.x + pane.cols.as_usize()) <= resize_border,
}); });
let resize_border = if result.is_empty() { let resize_border = if result.is_empty() {
match direction { match direction {
&L => pane_to_check.x(), &L => pane_to_check.x,
&D => pane_to_check.y() + pane_to_check.rows(), &D => pane_to_check.y + pane_to_check.rows.as_usize(),
&U => pane_to_check.y(), &U => pane_to_check.y,
&R => pane_to_check.x() + pane_to_check.cols(), &R => pane_to_check.x + pane_to_check.cols.as_usize(),
} }
} else { } else {
resize_border resize_border
}; };
let pane_ids: Vec<PaneId> = result.iter().map(|t| t.pid()).collect(); let pane_ids: Vec<PaneId> = result.iter().map(|(pid, _t)| *pid).collect();
Ok((resize_border, pane_ids)) Ok((resize_border, pane_ids))
} }
@ -770,9 +827,8 @@ impl<'a> TiledPaneGrid<'a> {
left_border_x: usize, left_border_x: usize,
right_border_x: usize, right_border_x: usize,
) -> bool { ) -> bool {
let panes = self.panes.borrow(); let pane = self.get_pane_geom(id).unwrap();
let pane = panes.get(id).unwrap(); pane.x >= left_border_x && pane.x + pane.cols.as_usize() <= right_border_x
pane.x() >= left_border_x && pane.x() + pane.cols() <= right_border_x
} }
fn pane_is_between_horizontal_borders( fn pane_is_between_horizontal_borders(
@ -781,9 +837,8 @@ impl<'a> TiledPaneGrid<'a> {
top_border_y: usize, top_border_y: usize,
bottom_border_y: usize, bottom_border_y: usize,
) -> bool { ) -> bool {
let panes = self.panes.borrow(); let pane = self.get_pane_geom(id).unwrap();
let pane = panes.get(id).unwrap(); pane.y >= top_border_y && pane.y + pane.rows.as_usize() <= bottom_border_y
pane.y() >= top_border_y && pane.y() + pane.rows() <= bottom_border_y
} }
fn viewport_pane_ids_directly_above(&self, pane_id: &PaneId) -> Vec<PaneId> { fn viewport_pane_ids_directly_above(&self, pane_id: &PaneId) -> Vec<PaneId> {
@ -863,7 +918,7 @@ impl<'a> TiledPaneGrid<'a> {
.filter(|(_, p)| p.selectable()) .filter(|(_, p)| p.selectable())
.map(|(p_id, p)| (*p_id, p)) .map(|(p_id, p)| (*p_id, p))
.collect(); .collect();
let next_index = panes let next_pane = panes
.iter() .iter()
.enumerate() .enumerate()
.filter(|(_, (_, c))| { .filter(|(_, (_, c))| {
@ -871,9 +926,83 @@ impl<'a> TiledPaneGrid<'a> {
&& c.horizontally_overlaps_with(Box::as_ref(current_pane)) && c.horizontally_overlaps_with(Box::as_ref(current_pane))
}) })
.max_by_key(|(_, (_, c))| c.active_at()) .max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid) .map(|(_, (_, pane))| pane);
.copied(); let next_pane_is_stacked = next_pane
next_index .map(|p| p.current_geom().is_stacked)
.unwrap_or(false);
if next_pane_is_stacked {
if let Some(next_pane_id) = next_pane.map(|p| p.pid()) {
return StackedPanes::new(self.panes.clone())
.flexible_pane_id_in_stack(&next_pane_id);
}
}
next_pane.map(|p| p.pid())
}
pub fn progress_stack_up_if_in_stack(&mut self, source_pane_id: &PaneId) -> Option<PaneId> {
let destination_pane_id_in_stack = {
let panes = self.panes.borrow();
let source_pane = panes.get(source_pane_id)?;
let pane_list: Vec<(PaneId, &&mut Box<dyn Pane>)> = panes
.iter()
.filter(|(_, p)| p.selectable())
.map(|(p_id, p)| (*p_id, p))
.collect();
let destination_pane_id = pane_list
.iter()
.enumerate()
.filter(|(_, (_, c))| {
c.is_directly_above(Box::as_ref(source_pane))
&& c.vertically_overlaps_with(Box::as_ref(source_pane))
&& c.current_geom().is_stacked
})
.max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid)
.copied();
destination_pane_id
};
match destination_pane_id_in_stack {
Some(destination_pane_id) => {
StackedPanes::new(self.panes.clone())
.move_up(source_pane_id, &destination_pane_id)
.ok()?;
Some(destination_pane_id)
},
None => None,
}
}
pub fn progress_stack_down_if_in_stack(&mut self, source_pane_id: &PaneId) -> Option<PaneId> {
let destination_pane_id_in_stack = {
let panes = self.panes.borrow();
let source_pane = panes.get(source_pane_id)?;
let pane_list: Vec<(PaneId, &&mut Box<dyn Pane>)> = panes
.iter()
.filter(|(_, p)| p.selectable())
.map(|(p_id, p)| (*p_id, p))
.collect();
let destination_pane_id = pane_list
.iter()
.enumerate()
.filter(|(_, (_, c))| {
c.is_directly_below(Box::as_ref(source_pane))
&& c.vertically_overlaps_with(Box::as_ref(source_pane))
&& c.current_geom().is_stacked
})
.max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid)
.copied();
destination_pane_id
};
match destination_pane_id_in_stack {
Some(destination_pane_id) => {
StackedPanes::new(self.panes.clone())
.move_down(source_pane_id, &destination_pane_id)
.ok()?;
Some(destination_pane_id)
},
None => None,
}
} }
pub fn next_selectable_pane_id_below(&self, current_pane_id: &PaneId) -> Option<PaneId> { pub fn next_selectable_pane_id_below(&self, current_pane_id: &PaneId) -> Option<PaneId> {
let panes = self.panes.borrow(); let panes = self.panes.borrow();
@ -889,6 +1018,7 @@ impl<'a> TiledPaneGrid<'a> {
.filter(|(_, (_, c))| { .filter(|(_, (_, c))| {
c.is_directly_below(Box::as_ref(current_pane)) c.is_directly_below(Box::as_ref(current_pane))
&& c.vertically_overlaps_with(Box::as_ref(current_pane)) && c.vertically_overlaps_with(Box::as_ref(current_pane))
&& !c.current_geom().is_stacked
}) })
.max_by_key(|(_, (_, c))| c.active_at()) .max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid) .map(|(_, (pid, _))| pid)
@ -909,6 +1039,7 @@ impl<'a> TiledPaneGrid<'a> {
.filter(|(_, (_, c))| { .filter(|(_, (_, c))| {
c.is_directly_above(Box::as_ref(current_pane)) c.is_directly_above(Box::as_ref(current_pane))
&& c.vertically_overlaps_with(Box::as_ref(current_pane)) && c.vertically_overlaps_with(Box::as_ref(current_pane))
&& !c.current_geom().is_stacked
}) })
.max_by_key(|(_, (_, c))| c.active_at()) .max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid) .map(|(_, (pid, _))| pid)
@ -923,7 +1054,7 @@ impl<'a> TiledPaneGrid<'a> {
.filter(|(_, p)| p.selectable()) .filter(|(_, p)| p.selectable())
.map(|(p_id, p)| (*p_id, p)) .map(|(p_id, p)| (*p_id, p))
.collect(); .collect();
let next_index = panes let next_pane = panes
.iter() .iter()
.enumerate() .enumerate()
.filter(|(_, (_, c))| { .filter(|(_, (_, c))| {
@ -931,16 +1062,33 @@ impl<'a> TiledPaneGrid<'a> {
&& c.horizontally_overlaps_with(Box::as_ref(current_pane)) && c.horizontally_overlaps_with(Box::as_ref(current_pane))
}) })
.max_by_key(|(_, (_, c))| c.active_at()) .max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid) .map(|(_, (_pid, pane))| pane)
.copied(); .copied();
next_index let next_pane_is_stacked = next_pane
.map(|p| p.current_geom().is_stacked)
.unwrap_or(false);
if next_pane_is_stacked {
if let Some(next_pane_id) = next_pane.map(|p| p.pid()) {
return StackedPanes::new(self.panes.clone())
.flexible_pane_id_in_stack(&next_pane_id);
}
}
next_pane.map(|p| p.pid())
} }
fn horizontal_borders(&self, pane_ids: &[PaneId]) -> HashSet<usize> { fn horizontal_borders(&self, pane_ids: &[PaneId]) -> HashSet<usize> {
pane_ids.iter().fold(HashSet::new(), |mut borders, p| { pane_ids.iter().fold(HashSet::new(), |mut borders, p| {
let panes = self.panes.borrow(); let panes = self.panes.borrow();
let pane = panes.get(p).unwrap(); let pane = panes.get(p).unwrap();
borders.insert(pane.y()); if pane.current_geom().is_stacked {
borders.insert(pane.y() + pane.rows()); let pane_geom = StackedPanes::new(self.panes.clone())
.position_and_size_of_stack(&pane.pid())
.unwrap();
borders.insert(pane_geom.y);
borders.insert(pane_geom.y + pane_geom.rows.as_usize());
} else {
borders.insert(pane.y());
borders.insert(pane.y() + pane.rows());
}
borders borders
}) })
} }
@ -1098,14 +1246,23 @@ impl<'a> TiledPaneGrid<'a> {
pub fn fill_space_over_pane(&mut self, id: PaneId) -> bool { pub fn fill_space_over_pane(&mut self, id: PaneId) -> bool {
// true => successfully filled space over pane // true => successfully filled space over pane
// false => didn't succeed, so didn't do anything // false => didn't succeed, so didn't do anything
let (freed_width, freed_height) = { let (freed_width, freed_height, pane_to_close_is_stacked) = {
let panes = self.panes.borrow_mut(); let panes = self.panes.borrow_mut();
let pane_to_close = panes.get(&id).unwrap(); let pane_to_close = panes.get(&id).unwrap();
let freed_space = pane_to_close.position_and_size(); let freed_space = pane_to_close.position_and_size();
let freed_width = freed_space.cols.as_percent(); let freed_width = freed_space.cols.as_percent();
let freed_height = freed_space.rows.as_percent(); let freed_height = freed_space.rows.as_percent();
(freed_width, freed_height) let pane_to_close_is_stacked = pane_to_close.current_geom().is_stacked;
(freed_width, freed_height, pane_to_close_is_stacked)
}; };
if pane_to_close_is_stacked {
let successfully_filled_space = StackedPanes::new(self.panes.clone())
.fill_space_over_pane_in_stack(&id)
.unwrap_or(false);
if successfully_filled_space {
return true;
}
}
if let (Some(freed_width), Some(freed_height)) = (freed_width, freed_height) { if let (Some(freed_width), Some(freed_height)) = (freed_width, freed_height) {
if let Some((panes_to_grow, direction)) = self.find_panes_to_grow(id) { if let Some((panes_to_grow, direction)) = self.find_panes_to_grow(id) {
self.grow_panes(&panes_to_grow, direction, (freed_width, freed_height)); self.grow_panes(&panes_to_grow, direction, (freed_width, freed_height));
@ -1126,8 +1283,10 @@ impl<'a> TiledPaneGrid<'a> {
cursor_height_width_ratio: Option<usize>, cursor_height_width_ratio: Option<usize>,
) -> Option<(PaneId, SplitDirection)> { ) -> Option<(PaneId, SplitDirection)> {
let panes = self.panes.borrow(); let panes = self.panes.borrow();
let pane_sequence: Vec<(&PaneId, &&mut Box<dyn Pane>)> = let pane_sequence: Vec<(&PaneId, &&mut Box<dyn Pane>)> = panes
panes.iter().filter(|(_, p)| p.selectable()).collect(); .iter()
.filter(|(_, p)| p.selectable() && !p.current_geom().is_stacked)
.collect();
let (_largest_pane_size, pane_id_to_split) = pane_sequence.iter().fold( let (_largest_pane_size, pane_id_to_split) = pane_sequence.iter().fold(
(0, None), (0, None),
|(current_largest_pane_size, current_pane_id_to_split), id_and_pane_to_check| { |(current_largest_pane_size, current_pane_id_to_split), id_and_pane_to_check| {

View file

@ -40,6 +40,7 @@ fn create_pane() -> TerminalPane {
Rc::new(RefCell::new(Palette::default())), Rc::new(RefCell::new(Palette::default())),
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
let content = read_fixture(); let content = read_fixture();
terminal_pane.handle_pty_bytes(content); terminal_pane.handle_pty_bytes(content);

View file

@ -48,6 +48,7 @@ pub fn scrolling_inside_a_pane() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..30 { for i in 0..30 {
@ -89,6 +90,7 @@ pub fn sixel_image_inside_terminal_pane() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
let sixel_image_bytes = "\u{1b}Pq let sixel_image_bytes = "\u{1b}Pq
#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0
@ -130,6 +132,7 @@ pub fn partial_sixel_image_inside_terminal_pane() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
let pane_content = read_fixture("sixel-image-500px.six"); let pane_content = read_fixture("sixel-image-500px.six");
terminal_pane.handle_pty_bytes(pane_content); terminal_pane.handle_pty_bytes(pane_content);
@ -165,6 +168,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
let pane_content = read_fixture("sixel-image-500px.six"); let pane_content = read_fixture("sixel-image-500px.six");
terminal_pane.handle_pty_bytes(pane_content); terminal_pane.handle_pty_bytes(pane_content);
@ -199,6 +203,7 @@ pub fn scrolling_through_a_sixel_image() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..30 { for i in 0..30 {
@ -244,6 +249,7 @@ pub fn multiple_sixel_images_in_pane() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..5 { for i in 0..5 {
@ -287,6 +293,7 @@ pub fn resizing_pane_with_sixel_images() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..5 { for i in 0..5 {
@ -333,6 +340,7 @@ pub fn changing_character_cell_size_with_sixel_images() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..5 { for i in 0..5 {
@ -384,6 +392,7 @@ pub fn keep_working_after_corrupted_sixel_image() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
let sixel_image_bytes = "\u{1b}PI AM CORRUPTED BWAHAHAq let sixel_image_bytes = "\u{1b}PI AM CORRUPTED BWAHAHAq
@ -433,6 +442,7 @@ pub fn pane_with_frame_position_is_on_frame() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
terminal_pane.set_content_offset(Offset::frame(1)); terminal_pane.set_content_offset(Offset::frame(1));
@ -518,6 +528,7 @@ pub fn pane_with_bottom_and_right_borders_position_is_on_frame() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
terminal_pane.set_content_offset(Offset::shift(1, 1)); terminal_pane.set_content_offset(Offset::shift(1, 1));
@ -603,6 +614,7 @@ pub fn frameless_pane_position_is_on_frame() {
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
None, None,
None,
); // 0 is the pane index ); // 0 is the pane index
terminal_pane.set_content_offset(Offset::default()); terminal_pane.set_content_offset(Offset::default());

View file

@ -12,7 +12,7 @@ use zellij_utils::{
errors::{prelude::*, ContextType, PluginContext}, errors::{prelude::*, ContextType, PluginContext},
input::{ input::{
command::TerminalAction, command::TerminalAction,
layout::{FloatingPanesLayout, Layout, PaneLayout, Run, RunPlugin, RunPluginLocation}, layout::{FloatingPaneLayout, Layout, Run, RunPlugin, RunPluginLocation, TiledPaneLayout},
plugins::PluginsConfig, plugins::PluginsConfig,
}, },
pane_size::Size, pane_size::Size,
@ -28,8 +28,8 @@ pub enum PluginInstruction {
RemoveClient(ClientId), RemoveClient(ClientId),
NewTab( NewTab(
Option<TerminalAction>, Option<TerminalAction>,
Option<PaneLayout>, Option<TiledPaneLayout>,
Vec<FloatingPanesLayout>, Vec<FloatingPaneLayout>,
Option<String>, // tab name Option<String>, // tab name
usize, // tab_index usize, // tab_index
ClientId, ClientId,
@ -102,6 +102,11 @@ pub(crate) fn plugin_thread_main(
.unwrap_or_else(|| layout.new_tab().0) .unwrap_or_else(|| layout.new_tab().0)
.extract_run_instructions(); .extract_run_instructions();
let size = Size::default(); let size = Size::default();
let floating_panes_layout = if floating_panes_layout.is_empty() {
layout.new_tab().1
} else {
floating_panes_layout
};
let mut extracted_floating_plugins: Vec<Option<Run>> = floating_panes_layout let mut extracted_floating_plugins: Vec<Option<Run>> = floating_panes_layout
.iter() .iter()
.map(|f| f.run.clone()) .map(|f| f.run.clone())

View file

@ -15,7 +15,7 @@ use zellij_utils::{
errors::{ContextType, PtyContext}, errors::{ContextType, PtyContext},
input::{ input::{
command::{RunCommand, TerminalAction}, command::{RunCommand, TerminalAction},
layout::{FloatingPanesLayout, Layout, PaneLayout, Run, RunPluginLocation}, layout::{FloatingPaneLayout, Layout, Run, RunPluginLocation, TiledPaneLayout},
}, },
}; };
@ -49,8 +49,8 @@ pub enum PtyInstruction {
GoToTab(TabIndex, ClientId), GoToTab(TabIndex, ClientId),
NewTab( NewTab(
Option<TerminalAction>, Option<TerminalAction>,
Option<PaneLayout>, Option<TiledPaneLayout>,
Vec<FloatingPanesLayout>, Vec<FloatingPaneLayout>,
Option<String>, Option<String>,
usize, // tab_index usize, // tab_index
HashMap<RunPluginLocation, Vec<u32>>, // plugin_ids HashMap<RunPluginLocation, Vec<u32>>, // plugin_ids
@ -575,8 +575,8 @@ impl Pty {
} }
pub fn spawn_terminals_for_layout( pub fn spawn_terminals_for_layout(
&mut self, &mut self,
layout: PaneLayout, layout: TiledPaneLayout,
floating_panes_layout: Vec<FloatingPanesLayout>, floating_panes_layout: Vec<FloatingPaneLayout>,
default_shell: Option<TerminalAction>, default_shell: Option<TerminalAction>,
plugin_ids: HashMap<RunPluginLocation, Vec<u32>>, plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
tab_index: usize, tab_index: usize,

View file

@ -165,6 +165,12 @@ pub(crate) fn route_action(
.send_to_screen(screen_instr) .send_to_screen(screen_instr)
.with_context(err_context)?; .with_context(err_context)?;
}, },
Action::MovePaneBackwards => {
session
.senders
.send_to_screen(ScreenInstruction::MovePaneBackwards(client_id))
.with_context(err_context)?;
},
Action::DumpScreen(val, full) => { Action::DumpScreen(val, full) => {
session session
.senders .senders
@ -436,8 +442,18 @@ pub(crate) fn route_action(
.send_to_screen(ScreenInstruction::CloseFocusedPane(client_id)) .send_to_screen(ScreenInstruction::CloseFocusedPane(client_id))
.with_context(err_context)?; .with_context(err_context)?;
}, },
Action::NewTab(tab_layout, floating_panes_layout, tab_name) => { Action::NewTab(
tab_layout,
floating_panes_layout,
swap_tiled_layouts,
swap_floating_layouts,
tab_name,
) => {
let shell = session.default_shell.clone(); let shell = session.default_shell.clone();
let swap_tiled_layouts =
swap_tiled_layouts.unwrap_or_else(|| session.layout.swap_tiled_layouts.clone());
let swap_floating_layouts = swap_floating_layouts
.unwrap_or_else(|| session.layout.swap_floating_layouts.clone());
session session
.senders .senders
.send_to_screen(ScreenInstruction::NewTab( .send_to_screen(ScreenInstruction::NewTab(
@ -445,6 +461,7 @@ pub(crate) fn route_action(
tab_layout, tab_layout,
floating_panes_layout, floating_panes_layout,
tab_name, tab_name,
(swap_tiled_layouts, swap_floating_layouts),
client_id, client_id,
)) ))
.with_context(err_context)?; .with_context(err_context)?;
@ -480,10 +497,13 @@ pub(crate) fn route_action(
.with_context(err_context)?; .with_context(err_context)?;
}, },
Action::GoToTabName(name, create) => { Action::GoToTabName(name, create) => {
let swap_tiled_layouts = session.layout.swap_tiled_layouts.clone();
let swap_floating_layouts = session.layout.swap_floating_layouts.clone();
session session
.senders .senders
.send_to_screen(ScreenInstruction::GoToTabName( .send_to_screen(ScreenInstruction::GoToTabName(
name, name,
(swap_tiled_layouts, swap_floating_layouts),
create, create,
Some(client_id), Some(client_id),
)) ))
@ -626,6 +646,18 @@ pub(crate) fn route_action(
.with_context(err_context)?; .with_context(err_context)?;
}, },
Action::ToggleMouseMode => {}, // Handled client side Action::ToggleMouseMode => {}, // Handled client side
Action::PreviousSwapLayout => {
session
.senders
.send_to_screen(ScreenInstruction::PreviousSwapLayout(client_id))
.with_context(err_context)?;
},
Action::NextSwapLayout => {
session
.senders
.send_to_screen(ScreenInstruction::NextSwapLayout(client_id))
.with_context(err_context)?;
},
} }
Ok(should_break) Ok(should_break)
} }

View file

@ -12,7 +12,9 @@ use zellij_utils::input::options::Clipboard;
use zellij_utils::pane_size::{Size, SizeInPixels}; use zellij_utils::pane_size::{Size, SizeInPixels};
use zellij_utils::{ use zellij_utils::{
input::command::TerminalAction, input::command::TerminalAction,
input::layout::{FloatingPanesLayout, PaneLayout, RunPluginLocation}, input::layout::{
FloatingPaneLayout, RunPluginLocation, SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout,
},
position::Position, position::Position,
}; };
@ -149,6 +151,7 @@ pub enum ScreenInstruction {
MoveFocusRight(ClientId), MoveFocusRight(ClientId),
MoveFocusRightOrNextTab(ClientId), MoveFocusRightOrNextTab(ClientId),
MovePane(ClientId), MovePane(ClientId),
MovePaneBackwards(ClientId),
MovePaneUp(ClientId), MovePaneUp(ClientId),
MovePaneDown(ClientId), MovePaneDown(ClientId),
MovePaneRight(ClientId), MovePaneRight(ClientId),
@ -177,14 +180,15 @@ pub enum ScreenInstruction {
UndoRenamePane(ClientId), UndoRenamePane(ClientId),
NewTab( NewTab(
Option<TerminalAction>, Option<TerminalAction>,
Option<PaneLayout>, Option<TiledPaneLayout>,
Vec<FloatingPanesLayout>, Vec<FloatingPaneLayout>,
Option<String>, Option<String>,
(Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>), // swap layouts
ClientId, ClientId,
), ),
ApplyLayout( ApplyLayout(
PaneLayout, TiledPaneLayout,
Vec<FloatingPanesLayout>, Vec<FloatingPaneLayout>,
Vec<(u32, HoldForCommand)>, // new pane pids Vec<(u32, HoldForCommand)>, // new pane pids
Vec<(u32, HoldForCommand)>, // new floating pane pids Vec<(u32, HoldForCommand)>, // new floating pane pids
HashMap<RunPluginLocation, Vec<u32>>, HashMap<RunPluginLocation, Vec<u32>>,
@ -196,7 +200,12 @@ pub enum ScreenInstruction {
ToggleActiveSyncTab(ClientId), ToggleActiveSyncTab(ClientId),
CloseTab(ClientId), CloseTab(ClientId),
GoToTab(u32, Option<ClientId>), // this Option is a hacky workaround, please do not copy this behaviour GoToTab(u32, Option<ClientId>), // this Option is a hacky workaround, please do not copy this behaviour
GoToTabName(String, bool, Option<ClientId>), GoToTabName(
String,
(Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>), // swap layouts
bool,
Option<ClientId>,
),
ToggleTab(ClientId), ToggleTab(ClientId),
UpdateTabName(Vec<u8>, ClientId), UpdateTabName(Vec<u8>, ClientId),
UndoRenameTab(ClientId), UndoRenameTab(ClientId),
@ -231,6 +240,8 @@ pub enum ScreenInstruction {
SearchToggleWrap(ClientId), SearchToggleWrap(ClientId),
AddRedPaneFrameColorOverride(Vec<PaneId>, Option<String>), // Option<String> => optional error text AddRedPaneFrameColorOverride(Vec<PaneId>, Option<String>), // Option<String> => optional error text
ClearPaneFrameColorOverride(Vec<PaneId>), ClearPaneFrameColorOverride(Vec<PaneId>),
PreviousSwapLayout(ClientId),
NextSwapLayout(ClientId),
} }
impl From<&ScreenInstruction> for ScreenContext { impl From<&ScreenInstruction> for ScreenContext {
@ -286,6 +297,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenContext::MoveFocusRightOrNextTab ScreenContext::MoveFocusRightOrNextTab
}, },
ScreenInstruction::MovePane(..) => ScreenContext::MovePane, ScreenInstruction::MovePane(..) => ScreenContext::MovePane,
ScreenInstruction::MovePaneBackwards(..) => ScreenContext::MovePaneBackwards,
ScreenInstruction::MovePaneDown(..) => ScreenContext::MovePaneDown, ScreenInstruction::MovePaneDown(..) => ScreenContext::MovePaneDown,
ScreenInstruction::MovePaneUp(..) => ScreenContext::MovePaneUp, ScreenInstruction::MovePaneUp(..) => ScreenContext::MovePaneUp,
ScreenInstruction::MovePaneRight(..) => ScreenContext::MovePaneRight, ScreenInstruction::MovePaneRight(..) => ScreenContext::MovePaneRight,
@ -370,6 +382,8 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::ClearPaneFrameColorOverride(..) => { ScreenInstruction::ClearPaneFrameColorOverride(..) => {
ScreenContext::ClearPaneFrameColorOverride ScreenContext::ClearPaneFrameColorOverride
}, },
ScreenInstruction::PreviousSwapLayout(..) => ScreenContext::PreviousSwapLayout,
ScreenInstruction::NextSwapLayout(..) => ScreenContext::NextSwapLayout,
} }
} }
} }
@ -430,6 +444,7 @@ pub(crate) struct Screen {
default_mode_info: ModeInfo, // TODO: restructure ModeInfo to prevent this duplication default_mode_info: ModeInfo, // TODO: restructure ModeInfo to prevent this duplication
style: Style, style: Style,
draw_pane_frames: bool, draw_pane_frames: bool,
auto_layout: bool,
session_is_mirrored: bool, session_is_mirrored: bool,
copy_options: CopyOptions, copy_options: CopyOptions,
} }
@ -442,6 +457,7 @@ impl Screen {
max_panes: Option<usize>, max_panes: Option<usize>,
mode_info: ModeInfo, mode_info: ModeInfo,
draw_pane_frames: bool, draw_pane_frames: bool,
auto_layout: bool,
session_is_mirrored: bool, session_is_mirrored: bool,
copy_options: CopyOptions, copy_options: CopyOptions,
) -> Self { ) -> Self {
@ -463,6 +479,7 @@ impl Screen {
mode_info: BTreeMap::new(), mode_info: BTreeMap::new(),
default_mode_info: mode_info, default_mode_info: mode_info,
draw_pane_frames, draw_pane_frames,
auto_layout,
session_is_mirrored, session_is_mirrored,
copy_options, copy_options,
} }
@ -903,7 +920,12 @@ impl Screen {
} }
/// Creates a new [`Tab`] in this [`Screen`] /// Creates a new [`Tab`] in this [`Screen`]
pub fn new_tab(&mut self, tab_index: usize, client_id: ClientId) -> Result<()> { pub fn new_tab(
&mut self,
tab_index: usize,
swap_layouts: (Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>),
client_id: ClientId,
) -> Result<()> {
let err_context = || format!("failed to create new tab for client {client_id:?}",); let err_context = || format!("failed to create new tab for client {client_id:?}",);
let client_id = if self.get_active_tab(client_id).is_ok() { let client_id = if self.get_active_tab(client_id).is_ok() {
@ -932,20 +954,22 @@ impl Screen {
self.style, self.style,
self.default_mode_info.clone(), self.default_mode_info.clone(),
self.draw_pane_frames, self.draw_pane_frames,
self.auto_layout,
self.connected_clients.clone(), self.connected_clients.clone(),
self.session_is_mirrored, self.session_is_mirrored,
client_id, client_id,
self.copy_options.clone(), self.copy_options.clone(),
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
swap_layouts,
); );
self.tabs.insert(tab_index, tab); self.tabs.insert(tab_index, tab);
Ok(()) Ok(())
} }
pub fn apply_layout( pub fn apply_layout(
&mut self, &mut self,
layout: PaneLayout, layout: TiledPaneLayout,
floating_panes_layout: Vec<FloatingPanesLayout>, floating_panes_layout: Vec<FloatingPaneLayout>,
new_terminal_ids: Vec<(u32, HoldForCommand)>, new_terminal_ids: Vec<(u32, HoldForCommand)>,
new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>, new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
@ -1091,6 +1115,7 @@ impl Screen {
.copied() .copied()
.collect() .collect()
}; };
let (active_swap_layout_name, is_swap_layout_dirty) = tab.swap_layout_info();
tab_data.push(TabInfo { tab_data.push(TabInfo {
position: tab.position, position: tab.position,
name: tab.name.clone(), name: tab.name.clone(),
@ -1100,6 +1125,8 @@ impl Screen {
is_sync_panes_active: tab.is_sync_panes_active(), is_sync_panes_active: tab.is_sync_panes_active(),
are_floating_panes_visible: tab.are_floating_panes_visible(), are_floating_panes_visible: tab.are_floating_panes_visible(),
other_focused_clients, other_focused_clients,
active_swap_layout_name,
is_swap_layout_dirty,
}); });
} }
plugin_updates.push((None, Some(*client_id), Event::TabUpdate(tab_data))); plugin_updates.push((None, Some(*client_id), Event::TabUpdate(tab_data)));
@ -1355,6 +1382,7 @@ pub(crate) fn screen_thread_main(
) -> Result<()> { ) -> Result<()> {
let capabilities = config_options.simplified_ui; let capabilities = config_options.simplified_ui;
let draw_pane_frames = config_options.pane_frames.unwrap_or(true); let draw_pane_frames = config_options.pane_frames.unwrap_or(true);
let auto_layout = config_options.auto_layout.unwrap_or(true);
let session_is_mirrored = config_options.mirror_session.unwrap_or(false); let session_is_mirrored = config_options.mirror_session.unwrap_or(false);
let copy_options = CopyOptions::new( let copy_options = CopyOptions::new(
config_options.copy_command, config_options.copy_command,
@ -1374,6 +1402,7 @@ pub(crate) fn screen_thread_main(
}, },
), ),
draw_pane_frames, draw_pane_frames,
auto_layout,
session_is_mirrored, session_is_mirrored,
copy_options, copy_options,
); );
@ -1476,7 +1505,7 @@ pub(crate) fn screen_thread_main(
}, },
ScreenInstruction::ToggleFloatingPanes(client_id, default_shell) => { ScreenInstruction::ToggleFloatingPanes(client_id, default_shell) => {
active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab
.toggle_floating_panes(client_id, default_shell), ?); .toggle_floating_panes(Some(client_id), default_shell), ?);
screen.unblock_input()?; screen.unblock_input()?;
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
@ -1570,6 +1599,7 @@ pub(crate) fn screen_thread_main(
); );
screen.unblock_input()?; screen.unblock_input()?;
screen.render()?; screen.render()?;
screen.update_tabs()?; // TODO: no every time
}, },
ScreenInstruction::SwitchFocus(client_id) => { ScreenInstruction::SwitchFocus(client_id) => {
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
@ -1686,6 +1716,17 @@ pub(crate) fn screen_thread_main(
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane(client_id) |tab: &mut Tab, client_id: ClientId| tab.move_active_pane(client_id)
); );
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?;
screen.unblock_input()?;
},
ScreenInstruction::MovePaneBackwards(client_id) => {
active_tab_and_connected_client_id!(
screen,
client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_backwards(client_id)
);
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
@ -1695,6 +1736,7 @@ pub(crate) fn screen_thread_main(
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_down(client_id) |tab: &mut Tab, client_id: ClientId| tab.move_active_pane_down(client_id)
); );
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
@ -1704,6 +1746,7 @@ pub(crate) fn screen_thread_main(
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_up(client_id) |tab: &mut Tab, client_id: ClientId| tab.move_active_pane_up(client_id)
); );
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
@ -1713,6 +1756,7 @@ pub(crate) fn screen_thread_main(
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_right(client_id) |tab: &mut Tab, client_id: ClientId| tab.move_active_pane_right(client_id)
); );
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
@ -1722,6 +1766,7 @@ pub(crate) fn screen_thread_main(
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_left(client_id) |tab: &mut Tab, client_id: ClientId| tab.move_active_pane_left(client_id)
); );
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
@ -1851,12 +1896,16 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::ClosePane(id, client_id) => { ScreenInstruction::ClosePane(id, client_id) => {
match client_id { match client_id {
Some(client_id) => { Some(client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab.close_pane(id, false)); active_tab!(screen, client_id, |tab: &mut Tab| tab.close_pane(
id,
false,
Some(client_id)
));
}, },
None => { None => {
for tab in screen.tabs.values_mut() { for tab in screen.tabs.values_mut() {
if tab.get_all_pane_ids().contains(&id) { if tab.get_all_pane_ids().contains(&id) {
tab.close_pane(id, false); tab.close_pane(id, false, None);
break; break;
} }
} }
@ -1945,10 +1994,11 @@ pub(crate) fn screen_thread_main(
layout, layout,
floating_panes_layout, floating_panes_layout,
tab_name, tab_name,
swap_layouts,
client_id, client_id,
) => { ) => {
let tab_index = screen.get_new_tab_index(); let tab_index = screen.get_new_tab_index();
screen.new_tab(tab_index, client_id)?; screen.new_tab(tab_index, swap_layouts, client_id)?;
screen screen
.bus .bus
.senders .senders
@ -1999,7 +2049,7 @@ pub(crate) fn screen_thread_main(
screen.render()?; screen.render()?;
} }
}, },
ScreenInstruction::GoToTabName(tab_name, create, client_id) => { ScreenInstruction::GoToTabName(tab_name, swap_layouts, create, client_id) => {
let client_id = if client_id.is_none() { let client_id = if client_id.is_none() {
None None
} else if screen } else if screen
@ -2016,7 +2066,7 @@ pub(crate) fn screen_thread_main(
screen.render()?; screen.render()?;
if create && !tab_exists { if create && !tab_exists {
let tab_index = screen.get_new_tab_index(); let tab_index = screen.get_new_tab_index();
screen.new_tab(tab_index, client_id)?; screen.new_tab(tab_index, swap_layouts, client_id)?;
screen screen
.bus .bus
.senders .senders
@ -2044,6 +2094,7 @@ pub(crate) fn screen_thread_main(
}, },
ScreenInstruction::TerminalResize(new_size) => { ScreenInstruction::TerminalResize(new_size) => {
screen.resize_to_screen(new_size)?; screen.resize_to_screen(new_size)?;
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?; screen.render()?;
}, },
ScreenInstruction::TerminalPixelDimensions(pixel_dimensions) => { ScreenInstruction::TerminalPixelDimensions(pixel_dimensions) => {
@ -2256,6 +2307,28 @@ pub(crate) fn screen_thread_main(
} }
screen.render()?; screen.render()?;
}, },
ScreenInstruction::PreviousSwapLayout(client_id) => {
active_tab_and_connected_client_id!(
screen,
client_id,
|tab: &mut Tab, client_id: ClientId| tab.previous_swap_layout(Some(client_id)),
?
);
screen.render()?;
screen.update_tabs()?;
screen.unblock_input()?;
},
ScreenInstruction::NextSwapLayout(client_id) => {
active_tab_and_connected_client_id!(
screen,
client_id,
|tab: &mut Tab, client_id: ClientId| tab.next_swap_layout(Some(client_id), true),
?
);
screen.render()?;
screen.update_tabs()?;
screen.unblock_input()?;
},
} }
} }
Ok(()) Ok(())

View file

@ -14,11 +14,11 @@ use crate::{
ClientId, ClientId,
}; };
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::{BTreeMap, HashMap};
use std::rc::Rc; use std::rc::Rc;
use zellij_utils::{ use zellij_utils::{
data::{Palette, Style}, data::{Palette, Style},
input::layout::{FloatingPanesLayout, PaneLayout, Run, RunPluginLocation}, input::layout::{FloatingPaneLayout, Run, RunPluginLocation, TiledPaneLayout},
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport}, pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
}; };
@ -85,8 +85,8 @@ impl<'a> LayoutApplier<'a> {
} }
pub fn apply_layout( pub fn apply_layout(
&mut self, &mut self,
layout: PaneLayout, layout: TiledPaneLayout,
floating_panes_layout: Vec<FloatingPanesLayout>, floating_panes_layout: Vec<FloatingPaneLayout>,
new_terminal_ids: Vec<(u32, HoldForCommand)>, new_terminal_ids: Vec<(u32, HoldForCommand)>,
new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
mut new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>, mut new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
@ -103,28 +103,73 @@ impl<'a> LayoutApplier<'a> {
)?; )?;
return Ok(layout_has_floating_panes); return Ok(layout_has_floating_panes);
} }
pub fn apply_tiled_panes_layout_to_existing_panes(
&mut self,
layout: &TiledPaneLayout,
refocus_pane: bool,
client_id: Option<ClientId>,
) -> Result<()> {
let err_context = || format!("failed to apply tiled panes layout");
let free_space = self.total_space_for_tiled_panes();
let tiled_panes_count = self.tiled_panes.visible_panes_count();
match layout.position_panes_in_space(&free_space, Some(tiled_panes_count)) {
Ok(positions_in_layout) => {
let currently_focused_pane_id =
client_id.and_then(|client_id| self.tiled_panes.focused_pane_id(client_id));
let mut existing_tab_state =
ExistingTabState::new(self.tiled_panes.drain(), currently_focused_pane_id);
let mut pane_focuser = PaneFocuser::new(refocus_pane);
for (layout, position_and_size) in positions_in_layout {
if let Some(mut pane) = existing_tab_state.find_and_extract_pane(
&layout.run,
&position_and_size,
layout.focus.unwrap_or(false),
true,
) {
self.apply_layout_properties_to_pane(
&mut pane,
&layout,
Some(position_and_size),
);
pane_focuser.set_pane_id_in_focused_location(layout.focus, &pane);
resize_pty!(pane, self.os_api, self.senders)?;
self.tiled_panes
.add_pane_with_existing_geom(pane.pid(), pane);
}
}
let remaining_pane_ids: Vec<PaneId> = existing_tab_state.pane_ids();
for pane_id in remaining_pane_ids {
if let Some(mut pane) = existing_tab_state.remove_pane(&pane_id) {
self.apply_layout_properties_to_pane(&mut pane, &layout, None);
self.tiled_panes.insert_pane(pane.pid(), pane);
}
}
pane_focuser.focus_tiled_pane(&mut self.tiled_panes);
},
Err(e) => {
Err::<(), _>(anyError::msg(e))
.with_context(err_context)
.non_fatal(); // TODO: propagate this to the user
},
};
Ok(())
}
fn apply_tiled_panes_layout( fn apply_tiled_panes_layout(
&mut self, &mut self,
layout: PaneLayout, layout: TiledPaneLayout,
new_terminal_ids: Vec<(u32, HoldForCommand)>, new_terminal_ids: Vec<(u32, HoldForCommand)>,
new_plugin_ids: &mut HashMap<RunPluginLocation, Vec<u32>>, new_plugin_ids: &mut HashMap<RunPluginLocation, Vec<u32>>,
client_id: ClientId, client_id: ClientId,
) -> Result<()> { ) -> Result<()> {
let err_context = || format!("failed to apply tiled panes layout"); let err_context = || format!("failed to apply tiled panes layout");
let (viewport_cols, viewport_rows) = { let free_space = self.total_space_for_tiled_panes();
let viewport = self.viewport.borrow(); match layout.position_panes_in_space(&free_space, None) {
(viewport.cols, viewport.rows)
};
let mut free_space = PaneGeom::default();
free_space.cols.set_inner(viewport_cols);
free_space.rows.set_inner(viewport_rows);
match layout.position_panes_in_space(&free_space) {
Ok(positions_in_layout) => { Ok(positions_in_layout) => {
let positions_and_size = positions_in_layout.iter(); let positions_and_size = positions_in_layout.iter();
let mut new_terminal_ids = new_terminal_ids.iter(); let mut new_terminal_ids = new_terminal_ids.iter();
let mut focus_pane_id: Option<PaneId> = None; let mut focus_pane_id: Option<PaneId> = None;
let mut set_focus_pane_id = |layout: &PaneLayout, pane_id: PaneId| { let mut set_focus_pane_id = |layout: &TiledPaneLayout, pane_id: PaneId| {
if layout.focus.unwrap_or(false) && focus_pane_id.is_none() { if layout.focus.unwrap_or(false) && focus_pane_id.is_none() {
focus_pane_id = Some(pane_id); focus_pane_id = Some(pane_id);
} }
@ -154,6 +199,7 @@ impl<'a> LayoutApplier<'a> {
self.link_handler.clone(), self.link_handler.clone(),
self.character_cell_size.clone(), self.character_cell_size.clone(),
self.style, self.style,
layout.run.clone(),
); );
new_plugin.set_borderless(layout.borderless); new_plugin.set_borderless(layout.borderless);
self.tiled_panes self.tiled_panes
@ -180,6 +226,7 @@ impl<'a> LayoutApplier<'a> {
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
initial_title, initial_title,
layout.run.clone(),
); );
new_pane.set_borderless(layout.borderless); new_pane.set_borderless(layout.borderless);
if let Some(held_command) = hold_for_command { if let Some(held_command) = hold_for_command {
@ -216,7 +263,7 @@ impl<'a> LayoutApplier<'a> {
} }
fn apply_floating_panes_layout( fn apply_floating_panes_layout(
&mut self, &mut self,
floating_panes_layout: Vec<FloatingPanesLayout>, floating_panes_layout: Vec<FloatingPaneLayout>,
new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
new_plugin_ids: &mut HashMap<RunPluginLocation, Vec<u32>>, new_plugin_ids: &mut HashMap<RunPluginLocation, Vec<u32>>,
layout_name: Option<String>, layout_name: Option<String>,
@ -254,6 +301,7 @@ impl<'a> LayoutApplier<'a> {
self.link_handler.clone(), self.link_handler.clone(),
self.character_cell_size.clone(), self.character_cell_size.clone(),
self.style, self.style,
floating_pane_layout.run.clone(),
); );
new_pane.set_borderless(false); new_pane.set_borderless(false);
new_pane.set_content_offset(Offset::frame(1)); new_pane.set_content_offset(Offset::frame(1));
@ -285,6 +333,7 @@ impl<'a> LayoutApplier<'a> {
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
initial_title, initial_title,
floating_pane_layout.run.clone(),
); );
new_pane.set_borderless(false); new_pane.set_borderless(false);
new_pane.set_content_offset(Offset::frame(1)); new_pane.set_content_offset(Offset::frame(1));
@ -309,6 +358,80 @@ impl<'a> LayoutApplier<'a> {
Ok(false) Ok(false)
} }
} }
pub fn apply_floating_panes_layout_to_existing_panes(
&mut self,
floating_panes_layout: &Vec<FloatingPaneLayout>,
refocus_pane: bool,
client_id: Option<ClientId>,
) -> Result<bool> {
// true => has floating panes
let mut layout_has_floating_panes = false;
let layout_has_focused_pane = floating_panes_layout
.iter()
.find(|f| f.focus.map(|f| f).unwrap_or(false))
.is_some();
let floating_panes_layout = floating_panes_layout.iter();
let currently_focused_pane_id = self
.floating_panes
.active_pane_id_or_focused_pane_id(client_id);
let mut existing_tab_state =
ExistingTabState::new(self.floating_panes.drain(), currently_focused_pane_id);
let mut pane_focuser = PaneFocuser::new(refocus_pane);
for floating_pane_layout in floating_panes_layout {
let position_and_size = self
.floating_panes
.position_floating_pane_layout(&floating_pane_layout);
let is_focused = floating_pane_layout.focus.unwrap_or(false);
if let Some(mut pane) = existing_tab_state.find_and_extract_pane(
&floating_pane_layout.run,
&position_and_size,
is_focused,
false,
) {
layout_has_floating_panes = true;
self.apply_floating_pane_layout_properties_to_pane(
&mut pane,
Some(&floating_pane_layout),
position_and_size,
);
let pane_is_focused = floating_pane_layout
.focus
.or(Some(!layout_has_focused_pane));
pane_focuser.set_pane_id_in_focused_location(pane_is_focused, &pane);
resize_pty!(pane, self.os_api, self.senders)?;
self.floating_panes.add_pane(pane.pid(), pane);
}
}
let remaining_pane_ids: Vec<PaneId> = existing_tab_state.pane_ids();
for pane_id in remaining_pane_ids {
match self.floating_panes.find_room_for_new_pane() {
Some(position_and_size) => {
if let Some(mut pane) = existing_tab_state.remove_pane(&pane_id) {
layout_has_floating_panes = true;
self.apply_floating_pane_layout_properties_to_pane(
&mut pane,
None,
position_and_size,
);
pane_focuser
.set_pane_id_in_focused_location(Some(!layout_has_focused_pane), &pane);
resize_pty!(pane, self.os_api, self.senders)?;
self.floating_panes.add_pane(pane.pid(), pane);
}
},
None => {
log::error!("could not find room for pane!")
},
}
}
if layout_has_floating_panes {
pane_focuser.focus_floating_pane(&mut self.floating_panes, &mut self.os_api);
Ok(true)
} else {
Ok(false)
}
}
fn resize_whole_tab(&mut self, new_screen_size: Size) -> Result<()> { fn resize_whole_tab(&mut self, new_screen_size: Size) -> Result<()> {
let err_context = || { let err_context = || {
format!( format!(
@ -316,7 +439,6 @@ impl<'a> LayoutApplier<'a> {
new_screen_size new_screen_size
) )
}; };
self.floating_panes.resize(new_screen_size); self.floating_panes.resize(new_screen_size);
// we need to do this explicitly because floating_panes.resize does not do this // we need to do this explicitly because floating_panes.resize does not do this
self.floating_panes self.floating_panes
@ -385,4 +507,217 @@ impl<'a> LayoutApplier<'a> {
} }
} }
} }
fn apply_layout_properties_to_pane(
&self,
pane: &mut Box<dyn Pane>,
layout: &TiledPaneLayout,
position_and_size: Option<PaneGeom>,
) {
if let Some(position_and_size) = position_and_size {
pane.set_geom(position_and_size);
}
pane.set_borderless(layout.borderless);
if let Some(pane_title) = layout.name.as_ref() {
pane.set_title(pane_title.into());
}
}
fn apply_floating_pane_layout_properties_to_pane(
&self,
pane: &mut Box<dyn Pane>,
floating_pane_layout: Option<&FloatingPaneLayout>,
position_and_size: PaneGeom,
) {
pane.set_geom(position_and_size);
pane.set_borderless(false);
if let Some(pane_title) = floating_pane_layout.and_then(|f| f.name.clone()) {
pane.set_title(pane_title);
}
pane.set_content_offset(Offset::frame(1));
}
fn total_space_for_tiled_panes(&self) -> PaneGeom {
// for tiled panes we need to take the display area rather than the viewport because the
// viewport can potentially also be changed
let (display_area_cols, display_area_rows) = {
let display_area = self.display_area.borrow();
(display_area.cols, display_area.rows)
};
let mut free_space = PaneGeom::default();
free_space.cols.set_inner(display_area_cols);
free_space.rows.set_inner(display_area_rows);
free_space
}
}
struct ExistingTabState {
existing_panes: BTreeMap<PaneId, Box<dyn Pane>>,
currently_focused_pane_id: Option<PaneId>,
}
impl ExistingTabState {
pub fn new(
existing_panes: BTreeMap<PaneId, Box<dyn Pane>>,
currently_focused_pane_id: Option<PaneId>,
) -> Self {
ExistingTabState {
existing_panes,
currently_focused_pane_id,
}
}
pub fn find_and_extract_pane(
&mut self,
run: &Option<Run>,
position_and_size: &PaneGeom,
is_focused: bool,
default_to_closest_position: bool,
) -> Option<Box<dyn Pane>> {
let candidates = self.pane_candidates(run, position_and_size, default_to_closest_position);
if let Some(current_pane_id_with_same_contents) =
self.find_pane_id_with_same_contents(&candidates, run)
{
return self
.existing_panes
.remove(&current_pane_id_with_same_contents);
} else if let Some(currently_focused_pane_id) =
self.find_focused_pane_id(is_focused, &candidates)
{
return self.existing_panes.remove(&currently_focused_pane_id);
} else if let Some(same_position_candidate_id) = candidates
.iter()
.find(|(_, p)| p.position_and_size() == *position_and_size)
.map(|(pid, _p)| *pid)
.copied()
{
return self.existing_panes.remove(&same_position_candidate_id);
} else if let Some(first_candidate) =
candidates.iter().next().map(|(pid, _p)| *pid).copied()
{
return self.existing_panes.remove(&first_candidate);
}
None
}
pub fn pane_ids(&self) -> Vec<PaneId> {
self.existing_panes.keys().copied().collect()
}
pub fn remove_pane(&mut self, pane_id: &PaneId) -> Option<Box<dyn Pane>> {
self.existing_panes.remove(pane_id)
}
fn pane_candidates(
&self,
run: &Option<Run>,
position_and_size: &PaneGeom,
default_to_closest_position: bool,
) -> Vec<(&PaneId, &Box<dyn Pane>)> {
let mut candidates: Vec<_> = self.existing_panes.iter().collect();
candidates.sort_by(|(_a_id, a), (_b_id, b)| {
let a_invoked_with = a.invoked_with();
let b_invoked_with = b.invoked_with();
if Run::is_same_category(run, a_invoked_with)
&& !Run::is_same_category(run, b_invoked_with)
{
std::cmp::Ordering::Less
} else if Run::is_same_category(run, b_invoked_with)
&& !Run::is_same_category(run, a_invoked_with)
{
std::cmp::Ordering::Greater
} else if Run::is_terminal(a_invoked_with) && !Run::is_terminal(b_invoked_with) {
// we place terminals before everything else because when we can't find
// an exact match, we need to prefer terminals are more often than not
// we'd be doing the right thing here
std::cmp::Ordering::Less
} else if Run::is_terminal(b_invoked_with) && !Run::is_terminal(a_invoked_with) {
std::cmp::Ordering::Greater
} else {
// try to find the closest pane
if default_to_closest_position {
let abs = |a, b| (a as isize - b as isize).abs();
let a_x_distance = abs(a.position_and_size().x, position_and_size.x);
let a_y_distance = abs(a.position_and_size().y, position_and_size.y);
let b_x_distance = abs(b.position_and_size().x, position_and_size.x);
let b_y_distance = abs(b.position_and_size().y, position_and_size.y);
(a_x_distance + a_y_distance).cmp(&(b_x_distance + b_y_distance))
} else {
std::cmp::Ordering::Equal
}
}
});
candidates
}
fn find_focused_pane_id(
&self,
is_focused: bool,
candidates: &Vec<(&PaneId, &Box<dyn Pane>)>,
) -> Option<PaneId> {
if is_focused {
candidates
.iter()
.find(|(pid, _p)| Some(**pid) == self.currently_focused_pane_id)
.map(|(pid, _p)| *pid)
.copied()
} else {
None
}
}
fn find_pane_id_with_same_contents(
&self,
candidates: &Vec<(&PaneId, &Box<dyn Pane>)>,
run: &Option<Run>,
) -> Option<PaneId> {
candidates
.iter()
.find(|(_pid, p)| p.invoked_with() == run)
.map(|(pid, _p)| *pid)
.copied()
}
}
#[derive(Default, Debug)]
struct PaneFocuser {
refocus_pane: bool,
pane_id_in_focused_location: Option<PaneId>,
}
impl PaneFocuser {
pub fn new(refocus_pane: bool) -> Self {
PaneFocuser {
refocus_pane,
..Default::default()
}
}
pub fn set_pane_id_in_focused_location(
&mut self,
is_focused: Option<bool>,
pane: &Box<dyn Pane>,
) {
if is_focused.unwrap_or(false) && pane.selectable() {
self.pane_id_in_focused_location = Some(pane.pid());
}
}
pub fn focus_tiled_pane(&self, tiled_panes: &mut TiledPanes) {
match self.pane_id_in_focused_location {
Some(pane_id_in_focused_location) => {
if self.refocus_pane {
tiled_panes.reapply_pane_focus();
tiled_panes.switch_active_pane_with(pane_id_in_focused_location);
} else {
tiled_panes.reapply_pane_focus();
}
},
None => {
tiled_panes.reapply_pane_focus();
},
}
}
pub fn focus_floating_pane(
&self,
floating_panes: &mut FloatingPanes,
os_api: &mut Box<dyn ServerOsApi>,
) {
floating_panes.reapply_pane_focus();
if let Some(pane_id_in_focused_location) = self.pane_id_in_focused_location {
if self.refocus_pane {
floating_panes.switch_active_pane_with(os_api, pane_id_in_focused_location);
}
}
}
} }

View file

@ -4,6 +4,7 @@
mod clipboard; mod clipboard;
mod copy_command; mod copy_command;
mod layout_applier; mod layout_applier;
mod swap_layouts;
use copy_command::CopyCommand; use copy_command::CopyCommand;
use std::env::temp_dir; use std::env::temp_dir;
@ -19,6 +20,7 @@ use crate::pty_writer::PtyWriteInstruction;
use crate::screen::CopyOptions; use crate::screen::CopyOptions;
use crate::ui::pane_boundaries_frame::FrameParams; use crate::ui::pane_boundaries_frame::FrameParams;
use layout_applier::LayoutApplier; use layout_applier::LayoutApplier;
use swap_layouts::SwapLayouts;
use self::clipboard::ClipboardProvider; use self::clipboard::ClipboardProvider;
use crate::{ use crate::{
@ -44,7 +46,10 @@ use zellij_utils::{
data::{Event, InputMode, ModeInfo, Palette, PaletteColor, Style}, data::{Event, InputMode, ModeInfo, Palette, PaletteColor, Style},
input::{ input::{
command::TerminalAction, command::TerminalAction,
layout::{FloatingPanesLayout, PaneLayout, RunPluginLocation}, layout::{
FloatingPaneLayout, Run, RunPluginLocation, SwapFloatingLayout, SwapTiledLayout,
TiledPaneLayout,
},
parse_keys, parse_keys,
}, },
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport}, pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
@ -108,6 +113,7 @@ pub(crate) struct Tab {
pub style: Style, pub style: Style,
connected_clients: Rc<RefCell<HashSet<ClientId>>>, connected_clients: Rc<RefCell<HashSet<ClientId>>>,
draw_pane_frames: bool, draw_pane_frames: bool,
auto_layout: bool,
pending_vte_events: HashMap<u32, Vec<VteBytes>>, pending_vte_events: HashMap<u32, Vec<VteBytes>>,
pub selecting_with_mouse: bool, // this is only pub for the tests TODO: remove this once we combine write_text_to_clipboard with render pub selecting_with_mouse: bool, // this is only pub for the tests TODO: remove this once we combine write_text_to_clipboard with render
link_handler: Rc<RefCell<LinkHandler>>, link_handler: Rc<RefCell<LinkHandler>>,
@ -125,7 +131,8 @@ pub(crate) struct Tab {
// cursor_shape_csi) // cursor_shape_csi)
is_pending: bool, // a pending tab is one that is still being loaded or otherwise waiting is_pending: bool, // a pending tab is one that is still being loaded or otherwise waiting
pending_instructions: Vec<BufferedTabInstruction>, // instructions that came while the tab was pending_instructions: Vec<BufferedTabInstruction>, // instructions that came while the tab was
// pending and need to be re-applied // pending and need to be re-applied
swap_layouts: SwapLayouts,
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize)]
@ -392,6 +399,8 @@ pub trait Pane {
fn add_red_pane_frame_color_override(&mut self, _error_text: Option<String>); fn add_red_pane_frame_color_override(&mut self, _error_text: Option<String>);
fn clear_pane_frame_color_override(&mut self); fn clear_pane_frame_color_override(&mut self);
fn frame_color_override(&self) -> Option<PaletteColor>; fn frame_color_override(&self) -> Option<PaletteColor>;
fn invoked_with(&self) -> &Option<Run>;
fn set_title(&mut self, title: String);
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -437,12 +446,14 @@ impl Tab {
style: Style, style: Style,
default_mode_info: ModeInfo, default_mode_info: ModeInfo,
draw_pane_frames: bool, draw_pane_frames: bool,
auto_layout: bool,
connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>, connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>,
session_is_mirrored: bool, session_is_mirrored: bool,
client_id: ClientId, client_id: ClientId,
copy_options: CopyOptions, copy_options: CopyOptions,
terminal_emulator_colors: Rc<RefCell<Palette>>, terminal_emulator_colors: Rc<RefCell<Palette>>,
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>, terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
swap_layouts: (Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>),
) -> Self { ) -> Self {
let name = if name.is_empty() { let name = if name.is_empty() {
format!("Tab #{}", index + 1) format!("Tab #{}", index + 1)
@ -489,6 +500,7 @@ impl Tab {
Some(command) => ClipboardProvider::Command(CopyCommand::new(command)), Some(command) => ClipboardProvider::Command(CopyCommand::new(command)),
None => ClipboardProvider::Osc52(copy_options.clipboard), None => ClipboardProvider::Osc52(copy_options.clipboard),
}; };
let swap_layouts = SwapLayouts::new(swap_layouts, display_area.clone());
Tab { Tab {
index, index,
@ -511,6 +523,7 @@ impl Tab {
mode_info, mode_info,
default_mode_info, default_mode_info,
draw_pane_frames, draw_pane_frames,
auto_layout,
pending_vte_events: HashMap::new(), pending_vte_events: HashMap::new(),
connected_clients, connected_clients,
selecting_with_mouse: false, selecting_with_mouse: false,
@ -525,18 +538,21 @@ impl Tab {
cursor_positions_and_shape: HashMap::new(), cursor_positions_and_shape: HashMap::new(),
is_pending: true, // will be switched to false once the layout is applied is_pending: true, // will be switched to false once the layout is applied
pending_instructions: vec![], pending_instructions: vec![],
swap_layouts,
} }
} }
pub fn apply_layout( pub fn apply_layout(
&mut self, &mut self,
layout: PaneLayout, layout: TiledPaneLayout,
floating_panes_layout: Vec<FloatingPanesLayout>, floating_panes_layout: Vec<FloatingPaneLayout>,
new_terminal_ids: Vec<(u32, HoldForCommand)>, new_terminal_ids: Vec<(u32, HoldForCommand)>,
new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>, new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
client_id: ClientId, client_id: ClientId,
) -> Result<()> { ) -> Result<()> {
self.swap_layouts
.set_base_layout((layout.clone(), floating_panes_layout.clone()));
let layout_has_floating_panes = LayoutApplier::new( let layout_has_floating_panes = LayoutApplier::new(
&self.viewport, &self.viewport,
&self.senders, &self.senders,
@ -563,14 +579,153 @@ impl Tab {
)?; )?;
if layout_has_floating_panes { if layout_has_floating_panes {
if !self.floating_panes.panes_are_visible() { if !self.floating_panes.panes_are_visible() {
self.toggle_floating_panes(client_id, None)?; self.toggle_floating_panes(Some(client_id), None)?;
} }
} }
self.tiled_panes.set_pane_frames(self.draw_pane_frames); self.tiled_panes.reapply_pane_frames();
self.is_pending = false; self.is_pending = false;
self.apply_buffered_instructions()?; self.apply_buffered_instructions()?;
Ok(()) Ok(())
} }
pub fn swap_layout_info(&self) -> (Option<String>, bool) {
if self.floating_panes.panes_are_visible() {
self.swap_layouts.floating_layout_info()
} else {
let selectable_tiled_panes =
self.tiled_panes.get_panes().filter(|(_, p)| p.selectable());
if selectable_tiled_panes.count() > 1 {
self.swap_layouts.tiled_layout_info()
} else {
// no layout for single pane
(None, false)
}
}
}
fn relayout_floating_panes(
&mut self,
client_id: Option<ClientId>,
search_backwards: bool,
refocus_pane: bool,
) -> Result<()> {
if let Some(layout_candidate) = self
.swap_layouts
.swap_floating_panes(&self.floating_panes, search_backwards)
{
LayoutApplier::new(
&self.viewport,
&self.senders,
&self.sixel_image_store,
&self.link_handler,
&self.terminal_emulator_colors,
&self.terminal_emulator_color_codes,
&self.character_cell_size,
&self.style,
&self.display_area,
&mut self.tiled_panes,
&mut self.floating_panes,
self.draw_pane_frames,
&mut self.focus_pane_id,
&self.os_api,
)
.apply_floating_panes_layout_to_existing_panes(
&layout_candidate,
refocus_pane,
client_id,
)?;
}
self.is_pending = false;
self.apply_buffered_instructions()?;
self.set_force_render();
Ok(())
}
fn relayout_tiled_panes(
&mut self,
client_id: Option<ClientId>,
search_backwards: bool,
refocus_pane: bool,
best_effort: bool,
) -> Result<()> {
if self.tiled_panes.fullscreen_is_active() {
self.tiled_panes.unset_fullscreen();
}
let refocus_pane = if self.swap_layouts.is_tiled_damaged() {
false
} else {
refocus_pane
};
if let Some(layout_candidate) = self
.swap_layouts
.swap_tiled_panes(&self.tiled_panes, search_backwards)
.or_else(|| {
if best_effort {
self.swap_layouts
.best_effort_tiled_layout(&self.tiled_panes)
} else {
None
}
})
{
LayoutApplier::new(
&self.viewport,
&self.senders,
&self.sixel_image_store,
&self.link_handler,
&self.terminal_emulator_colors,
&self.terminal_emulator_color_codes,
&self.character_cell_size,
&self.style,
&self.display_area,
&mut self.tiled_panes,
&mut self.floating_panes,
self.draw_pane_frames,
&mut self.focus_pane_id,
&self.os_api,
)
.apply_tiled_panes_layout_to_existing_panes(
&layout_candidate,
refocus_pane,
client_id,
)?;
}
self.tiled_panes.reapply_pane_frames();
self.is_pending = false;
self.apply_buffered_instructions()?;
let display_area = *self.display_area.borrow();
// we do this so that the new swap layout has a chance to pass through the constraint system
self.tiled_panes.resize(display_area);
self.should_clear_display_before_rendering = true;
Ok(())
}
pub fn previous_swap_layout(&mut self, client_id: Option<ClientId>) -> Result<()> {
// warning, here we cache resizes rather than sending them to the pty, we do that in
// apply_cached_resizes below - beware when bailing on this function early!
self.os_api.cache_resizes();
let search_backwards = true;
if self.floating_panes.panes_are_visible() {
self.relayout_floating_panes(client_id, search_backwards, true)?;
} else {
self.relayout_tiled_panes(client_id, search_backwards, true, false)?;
}
self.os_api.apply_cached_resizes();
Ok(())
}
pub fn next_swap_layout(
&mut self,
client_id: Option<ClientId>,
refocus_pane: bool,
) -> Result<()> {
// warning, here we cache resizes rather than sending them to the pty, we do that in
// apply_cached_resizes below - beware when bailing on this function early!
self.os_api.cache_resizes();
let search_backwards = false;
if self.floating_panes.panes_are_visible() {
self.relayout_floating_panes(client_id, search_backwards, refocus_pane)?;
} else {
self.relayout_tiled_panes(client_id, search_backwards, refocus_pane, false)?;
}
self.os_api.apply_cached_resizes();
Ok(())
}
pub fn apply_buffered_instructions(&mut self) -> Result<()> { pub fn apply_buffered_instructions(&mut self) -> Result<()> {
let buffered_instructions: Vec<BufferedTabInstruction> = let buffered_instructions: Vec<BufferedTabInstruction> =
self.pending_instructions.drain(..).collect(); self.pending_instructions.drain(..).collect();
@ -702,7 +857,7 @@ impl Tab {
if let Some(focused_floating_pane_id) = self.floating_panes.active_pane_id(client_id) { if let Some(focused_floating_pane_id) = self.floating_panes.active_pane_id(client_id) {
if self.tiled_panes.has_room_for_new_pane() { if self.tiled_panes.has_room_for_new_pane() {
let floating_pane_to_embed = self let floating_pane_to_embed = self
.close_pane(focused_floating_pane_id, true) .close_pane(focused_floating_pane_id, true, Some(client_id))
.with_context(|| format!( .with_context(|| format!(
"failed to find floating pane (ID: {focused_floating_pane_id:?}) to embed for client {client_id}", "failed to find floating pane (ID: {focused_floating_pane_id:?}) to embed for client {client_id}",
)) ))
@ -713,6 +868,13 @@ impl Tab {
self.tiled_panes self.tiled_panes
.focus_pane(focused_floating_pane_id, client_id); .focus_pane(focused_floating_pane_id, client_id);
self.hide_floating_panes(); self.hide_floating_panes();
if self.auto_layout && !self.swap_layouts.is_tiled_damaged() {
// only do this if we're already in this layout, otherwise it might be
// confusing and not what the user intends
self.swap_layouts.set_is_tiled_damaged(); // we do this so that we won't skip to the
// next layout
self.next_swap_layout(Some(client_id), true)?;
}
} }
} }
} else if let Some(focused_pane_id) = self.tiled_panes.focused_pane_id(client_id) { } else if let Some(focused_pane_id) = self.tiled_panes.focused_pane_id(client_id) {
@ -721,7 +883,9 @@ impl Tab {
// don't close the only pane on screen... // don't close the only pane on screen...
return Ok(()); return Ok(());
} }
if let Some(mut embedded_pane_to_float) = self.close_pane(focused_pane_id, true) { if let Some(mut embedded_pane_to_float) =
self.close_pane(focused_pane_id, true, Some(client_id))
{
if !embedded_pane_to_float.borderless() { if !embedded_pane_to_float.borderless() {
// floating panes always have a frame unless they're explicitly borderless // floating panes always have a frame unless they're explicitly borderless
embedded_pane_to_float.set_content_offset(Offset::frame(1)); embedded_pane_to_float.set_content_offset(Offset::frame(1));
@ -734,6 +898,13 @@ impl Tab {
.add_pane(focused_pane_id, embedded_pane_to_float); .add_pane(focused_pane_id, embedded_pane_to_float);
self.floating_panes.focus_pane(focused_pane_id, client_id); self.floating_panes.focus_pane(focused_pane_id, client_id);
self.show_floating_panes(); self.show_floating_panes();
if self.auto_layout && !self.swap_layouts.is_floating_damaged() {
// only do this if we're already in this layout, otherwise it might be
// confusing and not what the user intends
self.swap_layouts.set_is_floating_damaged(); // we do this so that we won't skip to the
// next layout
self.next_swap_layout(Some(client_id), true)?;
}
} }
} }
} }
@ -741,7 +912,7 @@ impl Tab {
} }
pub fn toggle_floating_panes( pub fn toggle_floating_panes(
&mut self, &mut self,
client_id: ClientId, client_id: Option<ClientId>,
default_shell: Option<TerminalAction>, default_shell: Option<TerminalAction>,
) -> Result<()> { ) -> Result<()> {
if self.floating_panes.panes_are_visible() { if self.floating_panes.panes_are_visible() {
@ -750,24 +921,34 @@ impl Tab {
} else { } else {
self.show_floating_panes(); self.show_floating_panes();
match self.floating_panes.last_floating_pane_id() { match self.floating_panes.last_floating_pane_id() {
Some(first_floating_pane_id) => { Some(first_floating_pane_id) => match client_id {
if !self.floating_panes.active_panes_contain(&client_id) { Some(client_id) => {
if !self.floating_panes.active_panes_contain(&client_id) {
self.floating_panes
.focus_pane(first_floating_pane_id, client_id);
}
},
None => {
self.floating_panes self.floating_panes
.focus_pane(first_floating_pane_id, client_id); .focus_pane_for_all_clients(first_floating_pane_id);
} },
}, },
None => { None => {
let name = None; let name = None;
let should_float = true; let should_float = true;
let client_id_or_tab_index = match client_id {
Some(client_id) => ClientOrTabIndex::ClientId(client_id),
None => ClientOrTabIndex::TabIndex(self.index),
};
let instruction = PtyInstruction::SpawnTerminal( let instruction = PtyInstruction::SpawnTerminal(
default_shell, default_shell,
Some(should_float), Some(should_float),
name, name,
ClientOrTabIndex::ClientId(client_id), client_id_or_tab_index,
); );
self.senders.send_to_pty(instruction).with_context(|| { self.senders
format!("failed to open a floating pane for client {client_id}") .send_to_pty(instruction)
})?; .with_context(|| format!("failed to open a floating pane for client"))?;
}, },
} }
self.floating_panes.set_force_render(); self.floating_panes.set_force_render();
@ -807,12 +988,21 @@ impl Tab {
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
initial_pane_title, initial_pane_title,
None,
); );
new_pane.set_active_at(Instant::now());
new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
resize_pty!(new_pane, self.os_api, self.senders).with_context(err_context)?; resize_pty!(new_pane, self.os_api, self.senders).with_context(err_context)?;
self.floating_panes.add_pane(pid, Box::new(new_pane)); self.floating_panes.add_pane(pid, Box::new(new_pane));
self.floating_panes.focus_pane_for_all_clients(pid); self.floating_panes.focus_pane_for_all_clients(pid);
} }
if self.auto_layout && !self.swap_layouts.is_floating_damaged() {
// only do this if we're already in this layout, otherwise it might be
// confusing and not what the user intends
self.swap_layouts.set_is_floating_damaged(); // we do this so that we won't skip to the
// next layout
self.next_swap_layout(client_id, true)?;
}
} }
} else { } else {
if self.tiled_panes.fullscreen_is_active() { if self.tiled_panes.fullscreen_is_active() {
@ -821,7 +1011,7 @@ impl Tab {
if self.tiled_panes.has_room_for_new_pane() { if self.tiled_panes.has_room_for_new_pane() {
if let PaneId::Terminal(term_pid) = pid { if let PaneId::Terminal(term_pid) = pid {
let next_terminal_position = self.get_next_terminal_position(); let next_terminal_position = self.get_next_terminal_position();
let new_terminal = TerminalPane::new( let mut new_terminal = TerminalPane::new(
term_pid, term_pid,
PaneGeom::default(), // the initial size will be set later PaneGeom::default(), // the initial size will be set later
self.style, self.style,
@ -833,7 +1023,9 @@ impl Tab {
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
initial_pane_title, initial_pane_title,
None,
); );
new_terminal.set_active_at(Instant::now());
self.tiled_panes.insert_pane(pid, Box::new(new_terminal)); self.tiled_panes.insert_pane(pid, Box::new(new_terminal));
self.should_clear_display_before_rendering = true; self.should_clear_display_before_rendering = true;
if let Some(client_id) = client_id { if let Some(client_id) = client_id {
@ -841,6 +1033,13 @@ impl Tab {
} }
} }
} }
if self.auto_layout && !self.swap_layouts.is_tiled_damaged() {
// only do this if we're already in this layout, otherwise it might be
// confusing and not what the user intends
self.swap_layouts.set_is_tiled_damaged(); // we do this so that we won't skip to the
// next layout
self.next_swap_layout(client_id, true)?;
}
} }
Ok(()) Ok(())
} }
@ -865,6 +1064,7 @@ impl Tab {
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
None, None,
None,
); );
new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the
// constructor so it won't be overrided // constructor so it won't be overrided
@ -934,11 +1134,13 @@ impl Tab {
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
initial_pane_title, initial_pane_title,
None,
); );
self.tiled_panes self.tiled_panes
.split_pane_horizontally(pid, Box::new(new_terminal), client_id); .split_pane_horizontally(pid, Box::new(new_terminal), client_id);
self.should_clear_display_before_rendering = true; self.should_clear_display_before_rendering = true;
self.tiled_panes.focus_pane(pid, client_id); self.tiled_panes.focus_pane(pid, client_id);
self.swap_layouts.set_is_tiled_damaged();
} }
} else { } else {
log::error!("No room to split pane horizontally"); log::error!("No room to split pane horizontally");
@ -946,7 +1148,7 @@ impl Tab {
self.senders self.senders
.send_to_background_jobs(BackgroundJob::DisplayPaneError( .send_to_background_jobs(BackgroundJob::DisplayPaneError(
vec![active_pane_id], vec![active_pane_id],
"TOO SMALL!".into(), "CAN'T SPLIT!".into(),
)) ))
.with_context(err_context)?; .with_context(err_context)?;
} }
@ -988,11 +1190,13 @@ impl Tab {
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
initial_pane_title, initial_pane_title,
None,
); );
self.tiled_panes self.tiled_panes
.split_pane_vertically(pid, Box::new(new_terminal), client_id); .split_pane_vertically(pid, Box::new(new_terminal), client_id);
self.should_clear_display_before_rendering = true; self.should_clear_display_before_rendering = true;
self.tiled_panes.focus_pane(pid, client_id); self.tiled_panes.focus_pane(pid, client_id);
self.swap_layouts.set_is_tiled_damaged();
} }
} else { } else {
log::error!("No room to split pane vertically"); log::error!("No room to split pane vertically");
@ -1000,7 +1204,7 @@ impl Tab {
self.senders self.senders
.send_to_background_jobs(BackgroundJob::DisplayPaneError( .send_to_background_jobs(BackgroundJob::DisplayPaneError(
vec![active_pane_id], vec![active_pane_id],
"TOO SMALL!".into(), "CAN'T SPLIT!".into(),
)) ))
.with_context(err_context)?; .with_context(err_context)?;
} }
@ -1285,7 +1489,7 @@ impl Tab {
.with_context(err_context)?; .with_context(err_context)?;
}, },
Some(AdjustedInput::CloseThisPane) => { Some(AdjustedInput::CloseThisPane) => {
self.close_pane(PaneId::Terminal(active_terminal_id), false); self.close_pane(PaneId::Terminal(active_terminal_id), false, None);
should_update_ui = true; should_update_ui = true;
}, },
None => {}, None => {},
@ -1522,19 +1726,35 @@ impl Tab {
selectable_tiled_panes.count() > 0 selectable_tiled_panes.count() > 0
} }
pub fn resize_whole_tab(&mut self, new_screen_size: Size) -> Result<()> { pub fn resize_whole_tab(&mut self, new_screen_size: Size) -> Result<()> {
// warning, here we cache resizes rather than sending them to the pty, we do that in
// apply_cached_resizes below - beware when bailing on this function early!
self.os_api.cache_resizes();
let err_context = || format!("failed to resize whole tab (index {})", self.index); let err_context = || format!("failed to resize whole tab (index {})", self.index);
self.floating_panes.resize(new_screen_size); self.floating_panes.resize(new_screen_size);
// we need to do this explicitly because floating_panes.resize does not do this // we need to do this explicitly because floating_panes.resize does not do this
self.floating_panes self.floating_panes
.resize_pty_all_panes(&mut self.os_api) .resize_pty_all_panes(&mut self.os_api)
.with_context(err_context)?; .with_context(err_context)?;
self.tiled_panes.resize(new_screen_size); self.tiled_panes.resize(new_screen_size);
if self.auto_layout && !self.swap_layouts.is_floating_damaged() {
// we do this only for floating panes, because the constraint system takes care of the
// tiled panes
self.swap_layouts.set_is_floating_damaged();
let _ = self.relayout_floating_panes(None, false, false);
}
if self.auto_layout && !self.swap_layouts.is_tiled_damaged() && !self.is_fullscreen_active()
{
self.swap_layouts.set_is_tiled_damaged();
let _ = self.relayout_tiled_panes(None, false, false, true);
}
self.should_clear_display_before_rendering = true; self.should_clear_display_before_rendering = true;
let _ = self.os_api.apply_cached_resizes();
Ok(()) Ok(())
} }
pub fn resize(&mut self, client_id: ClientId, strategy: ResizeStrategy) -> Result<()> { pub fn resize(&mut self, client_id: ClientId, strategy: ResizeStrategy) -> Result<()> {
let err_context = || format!("unable to resize pane"); let err_context = || format!("unable to resize pane");
self.swap_layouts.set_is_floating_damaged();
self.swap_layouts.set_is_tiled_damaged();
if self.floating_panes.panes_are_visible() { if self.floating_panes.panes_are_visible() {
let successfully_resized = self let successfully_resized = self
.floating_panes .floating_panes
@ -1691,11 +1911,35 @@ impl Tab {
if self.tiled_panes.fullscreen_is_active() { if self.tiled_panes.fullscreen_is_active() {
return; return;
} }
self.tiled_panes.move_active_pane(client_id); let search_backwards = false;
if self.floating_panes.panes_are_visible() {
self.floating_panes
.move_active_pane(search_backwards, &mut self.os_api, client_id);
} else {
self.tiled_panes
.move_active_pane(search_backwards, client_id);
}
}
pub fn move_active_pane_backwards(&mut self, client_id: ClientId) {
if !self.has_selectable_panes() {
return;
}
if self.tiled_panes.fullscreen_is_active() {
return;
}
let search_backwards = true;
if self.floating_panes.panes_are_visible() {
self.floating_panes
.move_active_pane(search_backwards, &mut self.os_api, client_id);
} else {
self.tiled_panes
.move_active_pane(search_backwards, client_id);
}
} }
pub fn move_active_pane_down(&mut self, client_id: ClientId) { pub fn move_active_pane_down(&mut self, client_id: ClientId) {
if self.floating_panes.panes_are_visible() { if self.floating_panes.panes_are_visible() {
self.floating_panes.move_active_pane_down(client_id); self.floating_panes.move_active_pane_down(client_id);
self.swap_layouts.set_is_floating_damaged();
self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind
} else { } else {
if !self.has_selectable_panes() { if !self.has_selectable_panes() {
@ -1710,6 +1954,7 @@ impl Tab {
pub fn move_active_pane_up(&mut self, client_id: ClientId) { pub fn move_active_pane_up(&mut self, client_id: ClientId) {
if self.floating_panes.panes_are_visible() { if self.floating_panes.panes_are_visible() {
self.floating_panes.move_active_pane_up(client_id); self.floating_panes.move_active_pane_up(client_id);
self.swap_layouts.set_is_floating_damaged();
self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind
} else { } else {
if !self.has_selectable_panes() { if !self.has_selectable_panes() {
@ -1724,6 +1969,7 @@ impl Tab {
pub fn move_active_pane_right(&mut self, client_id: ClientId) { pub fn move_active_pane_right(&mut self, client_id: ClientId) {
if self.floating_panes.panes_are_visible() { if self.floating_panes.panes_are_visible() {
self.floating_panes.move_active_pane_right(client_id); self.floating_panes.move_active_pane_right(client_id);
self.swap_layouts.set_is_floating_damaged();
self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind
} else { } else {
if !self.has_selectable_panes() { if !self.has_selectable_panes() {
@ -1738,6 +1984,7 @@ impl Tab {
pub fn move_active_pane_left(&mut self, client_id: ClientId) { pub fn move_active_pane_left(&mut self, client_id: ClientId) {
if self.floating_panes.panes_are_visible() { if self.floating_panes.panes_are_visible() {
self.floating_panes.move_active_pane_left(client_id); self.floating_panes.move_active_pane_left(client_id);
self.swap_layouts.set_is_floating_damaged();
self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind
} else { } else {
if !self.has_selectable_panes() { if !self.has_selectable_panes() {
@ -1756,7 +2003,7 @@ impl Tab {
self.senders self.senders
.send_to_pty(PtyInstruction::ClosePane(pid)) .send_to_pty(PtyInstruction::ClosePane(pid))
.context("failed to close down to max terminals")?; .context("failed to close down to max terminals")?;
self.close_pane(pid, false); self.close_pane(pid, false, None);
} }
} }
Ok(()) Ok(())
@ -1803,6 +2050,7 @@ impl Tab {
&mut self, &mut self,
id: PaneId, id: PaneId,
ignore_suppressed_panes: bool, ignore_suppressed_panes: bool,
client_id: Option<ClientId>,
) -> Option<Box<dyn Pane>> { ) -> Option<Box<dyn Pane>> {
// we need to ignore suppressed panes when we toggle a pane to be floating/embedded(tiled) // we need to ignore suppressed panes when we toggle a pane to be floating/embedded(tiled)
// this is because in that case, while we do use this logic, we're not actually closing the // this is because in that case, while we do use this logic, we're not actually closing the
@ -1829,6 +2077,15 @@ impl Tab {
} }
self.set_force_render(); self.set_force_render();
self.floating_panes.set_force_render(); self.floating_panes.set_force_render();
if self.auto_layout
&& !self.swap_layouts.is_floating_damaged()
&& self.floating_panes.visible_panes_count() > 0
{
self.swap_layouts.set_is_floating_damaged();
// only relayout if the user is already "in" a layout, otherwise this might be
// confusing
let _ = self.next_swap_layout(client_id, false);
}
closed_pane closed_pane
} else { } else {
if self.tiled_panes.fullscreen_is_active() { if self.tiled_panes.fullscreen_is_active() {
@ -1837,6 +2094,17 @@ impl Tab {
let closed_pane = self.tiled_panes.remove_pane(id); let closed_pane = self.tiled_panes.remove_pane(id);
self.set_force_render(); self.set_force_render();
self.tiled_panes.set_force_render(); self.tiled_panes.set_force_render();
let closed_pane_is_stacked = closed_pane
.as_ref()
.map(|p| p.position_and_size().is_stacked)
.unwrap_or(false);
if self.auto_layout && !self.swap_layouts.is_tiled_damaged() && !closed_pane_is_stacked
{
self.swap_layouts.set_is_tiled_damaged();
// only relayout if the user is already "in" a layout, otherwise this might be
// confusing
let _ = self.next_swap_layout(client_id, false);
}
closed_pane closed_pane
} }
} }
@ -1905,7 +2173,7 @@ impl Tab {
if self.floating_panes.panes_are_visible() { if self.floating_panes.panes_are_visible() {
if let Some(active_floating_pane_id) = self.floating_panes.active_pane_id(client_id) { if let Some(active_floating_pane_id) = self.floating_panes.active_pane_id(client_id) {
self.close_pane(active_floating_pane_id, false); self.close_pane(active_floating_pane_id, false, Some(client_id));
self.senders self.senders
.send_to_pty(PtyInstruction::ClosePane(active_floating_pane_id)) .send_to_pty(PtyInstruction::ClosePane(active_floating_pane_id))
.with_context(|| err_context(active_floating_pane_id))?; .with_context(|| err_context(active_floating_pane_id))?;
@ -1913,7 +2181,7 @@ impl Tab {
} }
} }
if let Some(active_pane_id) = self.tiled_panes.get_active_pane_id(client_id) { if let Some(active_pane_id) = self.tiled_panes.get_active_pane_id(client_id) {
self.close_pane(active_pane_id, false); self.close_pane(active_pane_id, false, Some(client_id));
self.senders self.senders
.send_to_pty(PtyInstruction::ClosePane(active_pane_id)) .send_to_pty(PtyInstruction::ClosePane(active_pane_id))
.with_context(|| err_context(active_pane_id))?; .with_context(|| err_context(active_pane_id))?;
@ -2212,6 +2480,7 @@ impl Tab {
.floating_panes .floating_panes
.move_pane_with_mouse(*position, search_selectable) .move_pane_with_mouse(*position, search_selectable)
{ {
self.swap_layouts.set_is_floating_damaged();
self.set_force_render(); self.set_force_render();
return Ok(()); return Ok(());
} }
@ -2472,6 +2741,7 @@ impl Tab {
.floating_panes .floating_panes
.move_pane_with_mouse(*position_on_screen, search_selectable) .move_pane_with_mouse(*position_on_screen, search_selectable)
{ {
self.swap_layouts.set_is_floating_damaged();
self.set_force_render(); self.set_force_render();
return Ok(!is_repeated); // we don't need to re-render in this case if the pane did not move return Ok(!is_repeated); // we don't need to re-render in this case if the pane did not move
// return; // return;

View file

@ -0,0 +1,284 @@
use crate::panes::{FloatingPanes, TiledPanes};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
use zellij_utils::{
input::layout::{
FloatingPaneLayout, LayoutConstraint, SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout,
},
pane_size::{PaneGeom, Size},
};
#[derive(Clone, Debug, Default)]
pub struct SwapLayouts {
swap_tiled_layouts: Vec<SwapTiledLayout>,
swap_floating_layouts: Vec<SwapFloatingLayout>,
current_floating_layout_position: usize,
current_tiled_layout_position: usize,
is_floating_damaged: bool,
is_tiled_damaged: bool,
display_area: Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout)
}
impl SwapLayouts {
pub fn new(
swap_layouts: (Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>),
display_area: Rc<RefCell<Size>>,
) -> Self {
let display_area = display_area.clone();
SwapLayouts {
swap_tiled_layouts: swap_layouts.0,
swap_floating_layouts: swap_layouts.1,
is_floating_damaged: false,
is_tiled_damaged: false,
display_area,
..Default::default()
}
}
pub fn set_base_layout(&mut self, layout: (TiledPaneLayout, Vec<FloatingPaneLayout>)) {
let mut base_swap_tiled_layout = BTreeMap::new();
let mut base_swap_floating_layout = BTreeMap::new();
let tiled_panes_count = layout.0.pane_count();
let floating_panes_count = layout.1.len();
// we set MaxPanes to the current panes in the layout, because the base layout is not
// intended to be progressive - i.e. to have additional panes added to it
// we still want to keep it around in case we'd like to swap layouts without adding panes
base_swap_tiled_layout.insert(LayoutConstraint::MaxPanes(tiled_panes_count), layout.0);
base_swap_floating_layout
.insert(LayoutConstraint::MaxPanes(floating_panes_count), layout.1);
self.swap_tiled_layouts
.insert(0, (base_swap_tiled_layout, Some("BASE".into())));
self.swap_floating_layouts
.insert(0, (base_swap_floating_layout, Some("BASE".into())));
self.current_tiled_layout_position = 0;
self.current_floating_layout_position = 0;
}
pub fn set_is_floating_damaged(&mut self) {
self.is_floating_damaged = true;
}
pub fn set_is_tiled_damaged(&mut self) {
self.is_tiled_damaged = true;
}
pub fn is_floating_damaged(&self) -> bool {
self.is_floating_damaged
}
pub fn is_tiled_damaged(&self) -> bool {
self.is_tiled_damaged
}
pub fn tiled_layout_info(&self) -> (Option<String>, bool) {
// (swap_layout_name, is_swap_layout_dirty)
match self
.swap_tiled_layouts
.iter()
.nth(self.current_tiled_layout_position)
{
Some(current_tiled_layout) => (
current_tiled_layout.1.clone().or_else(|| {
Some(format!(
"Layout #{}",
self.current_tiled_layout_position + 1
))
}),
self.is_tiled_damaged,
),
None => (None, self.is_tiled_damaged),
}
}
pub fn floating_layout_info(&self) -> (Option<String>, bool) {
// (swap_layout_name, is_swap_layout_dirty)
match self
.swap_floating_layouts
.iter()
.nth(self.current_floating_layout_position)
{
Some(current_floating_layout) => (
current_floating_layout.1.clone().or_else(|| {
Some(format!(
"Layout #{}",
self.current_floating_layout_position + 1
))
}),
self.is_floating_damaged,
),
None => (None, self.is_floating_damaged),
}
}
pub fn swap_floating_panes(
&mut self,
floating_panes: &FloatingPanes,
search_backwards: bool,
) -> Option<Vec<FloatingPaneLayout>> {
if self.swap_floating_layouts.is_empty() {
return None;
}
let initial_position = self.current_floating_layout_position;
macro_rules! progress_layout {
() => {{
if search_backwards {
if self.current_floating_layout_position == 0 {
self.current_floating_layout_position =
self.swap_floating_layouts.len().saturating_sub(1);
} else {
self.current_floating_layout_position -= 1;
}
} else {
self.current_floating_layout_position += 1;
}
}};
}
if !self.is_floating_damaged
&& self
.swap_floating_layouts
.iter()
.nth(self.current_floating_layout_position)
.is_some()
{
progress_layout!();
}
self.is_floating_damaged = false;
loop {
match self
.swap_floating_layouts
.iter()
.nth(self.current_floating_layout_position)
{
Some(swap_layout) => {
for (constraint, layout) in swap_layout.0.iter() {
if self.state_fits_floating_panes_constraint(constraint, floating_panes) {
return Some(layout.clone());
};
}
progress_layout!();
},
None => {
self.current_floating_layout_position = 0;
},
};
if self.current_floating_layout_position == initial_position {
break;
}
}
None
}
fn state_fits_tiled_panes_constraint(
&self,
constraint: &LayoutConstraint,
tiled_panes: &TiledPanes,
) -> bool {
match constraint {
LayoutConstraint::MaxPanes(max_panes) => {
tiled_panes.visible_panes_count() <= *max_panes
},
LayoutConstraint::MinPanes(min_panes) => {
tiled_panes.visible_panes_count() >= *min_panes
},
LayoutConstraint::NoConstraint => true,
}
}
fn state_fits_floating_panes_constraint(
&self,
constraint: &LayoutConstraint,
floating_panes: &FloatingPanes,
) -> bool {
match constraint {
LayoutConstraint::MaxPanes(max_panes) => {
floating_panes.visible_panes_count() <= *max_panes
},
LayoutConstraint::MinPanes(min_panes) => {
floating_panes.visible_panes_count() >= *min_panes
},
LayoutConstraint::NoConstraint => true,
}
}
pub fn swap_tiled_panes(
&mut self,
tiled_panes: &TiledPanes,
search_backwards: bool,
) -> Option<TiledPaneLayout> {
if self.swap_tiled_layouts.is_empty() {
return None;
}
macro_rules! progress_layout {
() => {{
if search_backwards {
if self.current_tiled_layout_position == 0 {
self.current_tiled_layout_position =
self.swap_tiled_layouts.len().saturating_sub(1);
} else {
self.current_tiled_layout_position -= 1;
}
} else {
self.current_tiled_layout_position += 1;
}
}};
}
let initial_position = self.current_tiled_layout_position;
if !self.is_tiled_damaged
&& self
.swap_tiled_layouts
.iter()
.nth(self.current_tiled_layout_position)
.is_some()
{
progress_layout!();
}
self.is_tiled_damaged = false;
loop {
match self
.swap_tiled_layouts
.iter()
.nth(self.current_tiled_layout_position)
{
Some(swap_layout) => {
for (constraint, layout) in swap_layout.0.iter() {
if self.state_fits_tiled_panes_constraint(constraint, tiled_panes) {
let display_area = self.display_area.borrow();
// TODO: reuse the assets from position_panes_in_space here?
let pane_count = tiled_panes.visible_panes_count();
let display_area = PaneGeom::from(&*display_area);
if layout
.position_panes_in_space(&display_area, Some(pane_count))
.is_ok()
{
return Some(layout.clone());
}
};
}
progress_layout!();
},
None => {
self.current_tiled_layout_position = 0;
},
};
if self.current_tiled_layout_position == initial_position {
break;
}
}
None
}
pub fn best_effort_tiled_layout(
&mut self,
tiled_panes: &TiledPanes,
) -> Option<TiledPaneLayout> {
for swap_layout in self.swap_tiled_layouts.iter() {
for (_constraint, layout) in swap_layout.0.iter() {
let display_area = self.display_area.borrow();
// TODO: reuse the assets from position_panes_in_space here?
let pane_count = tiled_panes.visible_panes_count();
let display_area = PaneGeom::from(&*display_area);
if layout
.position_panes_in_space(&display_area, Some(pane_count))
.is_ok()
{
return Some(layout.clone());
}
}
}
log::error!("Could not find layout that would fit on screen!");
None
}
}

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5472
expression: snapshot
---
00 (C): ┌ tab-bar ────────────────────────────────────────┌ status-bar ──────────────────────────────────────────────┐──────────┐
01 (C): │I am a tab bar │I am a │ │
02 (C): │ │status bar │ │
03 (C): │ │ │ │
04 (C): │ │ │ │
05 (C): │ ┌ Pane #3 ──────────│ │ │
06 (C): │ │ │ │ │
07 (C): │ │ │ │ │
08 (C): │ │ │ │ │
09 (C): └─────────────────────────────│ └──────────────────────────────────────────────────────────┘ │
10 (C): ┌ command1 ───────────────────│ ┌ command2 ────────────────────────────────────────────────┐ │
11 (C): │ │ │ │ │
12 (C): │ │ │ │ │
13 (C): │ │ │ │ │
14 (C): │ Waiting to ru└───────────────────│ Waiting to run: command2 │ │
15 (C): │ │ │ │
16 (C): │ <ENTER> to run, <Ctrl-c> to exit │ <ENTER> to run, <Ctrl-c> to exit │ │
17 (C): │ │ │ │
18 (C): │ │ │ │
19 (C): └─────────────────────────────────────────────────└──────────────────────────────────────────────────────────┘──────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4762
expression: snapshot
---
00 (C): I am a tab bar
01 (C): ┌ command2 ─────────────────────────────┐┌ Pane #2 ─────────────────────────────┐┌ command1 ────────────────────────────┐
02 (C): │ ││ ││ │
03 (C): │ ││ ││ │
04 (C): │ ││ ││ │
05 (C): │ ││ ││ │
06 (C): │ ││ ││ │
07 (C): │ ││ ││ │
08 (C): │ Waiting to run: command2 ││ ││ Waiting to run: command1 │
09 (C): │ ││ ││ │
10 (C): │ <ENTER> to run, <Ctrl-c> to exit ││ ││ <ENTER> to run, <Ctrl-c> to exit │
11 (C): │ ││ ││ │
12 (C): │ ││ ││ │
13 (C): │ ││ ││ │
14 (C): │ ││ ││ │
15 (C): │ ││ ││ │
16 (C): │ ││ ││ │
17 (C): └───────────────────────────────────────┘└──────────────────────────────────────┘└──────────────────────────────────────┘
18 (C): I am a
19 (C): status bar

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3955
expression: snapshot
---
00 (C): ┌ Pane #1 ────────────────────────────────────────────────────────┐┌ Pane #2 ───────────────────────────────────────────┐
01 (C): │ │┌ Pane #3 ───────────────────────────────────────────┐
02 (C): │ │┌ Pane #4 ───────────────────────────────────────────┐
03 (C): │ │┌ Pane #5 ───────────────────────────────────────────┐
04 (C): │ │┌ Pane #6 ───────────────────────────────────────────┐
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └─────────────────────────────────────────────────────────────────┘└────────────────────────────────────────────────────┘

View file

@ -0,0 +1,46 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4062
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): │ │
10 (C): │ │
11 (C): │ │
12 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
13 (C): ┌ Pane #2 ────────────────────────────────────────────────────────┐┌ Pane #4 ───────────────────────────────────────────┐
14 (C): │ │┌ Pane #5 ───────────────────────────────────────────┐
15 (C): │ │┌ Pane #6 ───────────────────────────────────────────┐
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): │ ││ │
20 (C): │ ││ │
21 (C): │ ││ │
22 (C): │ ││ │
23 (C): │ ││ │
24 (C): │ ││ │
25 (C): │ ││ │
26 (C): │ ││ │
27 (C): └─────────────────────────────────────────────────────────────────┘└────────────────────────────────────────────────────┘
28 (C): ┌ Pane #3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
29 (C): │ │
30 (C): │ │
31 (C): │ │
32 (C): │ │
33 (C): │ │
34 (C): │ │
35 (C): │ │
36 (C): │ │
37 (C): │ │
38 (C): │ │
39 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,46 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4047
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): │ │
10 (C): │ │
11 (C): │ │
12 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
13 (C): ┌ Pane #2 ──────────────────────────────────────────────────┐┌ Pane #4 ─────────────────────────────────────────────────┐
14 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
15 (C): │ │┌ Pane #6 ─────────────────────────────────────────────────┐
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): │ ││ │
20 (C): │ ││ │
21 (C): │ ││ │
22 (C): │ ││ │
23 (C): │ ││ │
24 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
25 (C): ┌ Pane #3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
26 (C): │ │
27 (C): │ │
28 (C): │ │
29 (C): │ │
30 (C): │ │
31 (C): │ │
32 (C): │ │
33 (C): │ │
34 (C): │ │
35 (C): │ │
36 (C): │ │
37 (C): │ │
38 (C): │ │
39 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3716
expression: snapshot
---
00 (C): ┌ Pane #1 ───────────────────────────────────────────┐┌ Pane #2 ────────────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #3 ────────────────────────────────────────────────────────┐
02 (C): │ │┌ Pane #4 ────────────────────────────────────────────────────────┐
03 (C): │ │┌ Pane #5 ────────────────────────────────────────────────────────┐
04 (C): │ │┌ Pane #6 ────────────────────────────────────────────────────────┐
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,46 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3823
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): │ │
10 (C): │ │
11 (C): │ │
12 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
13 (C): ┌ Pane #2 ───────────────────────────────────────────┐┌ Pane #4 ────────────────────────────────────────────────────────┐
14 (C): │ │┌ Pane #5 ────────────────────────────────────────────────────────┐
15 (C): │ │┌ Pane #6 ────────────────────────────────────────────────────────┐
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): │ ││ │
20 (C): │ ││ │
21 (C): │ ││ │
22 (C): │ ││ │
23 (C): │ ││ │
24 (C): │ ││ │
25 (C): │ ││ │
26 (C): │ ││ │
27 (C): └────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────┘
28 (C): ┌ Pane #3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
29 (C): │ │
30 (C): │ │
31 (C): │ │
32 (C): │ │
33 (C): │ │
34 (C): │ │
35 (C): │ │
36 (C): │ │
37 (C): │ │
38 (C): │ │
39 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,46 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3807
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): │ │
10 (C): │ │
11 (C): │ │
12 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
13 (C): ┌ Pane #2 ──────────────────────────────────────────────────┐┌ Pane #4 ─────────────────────────────────────────────────┐
14 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
15 (C): │ │┌ Pane #6 ─────────────────────────────────────────────────┐
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): │ ││ │
20 (C): │ ││ │
21 (C): │ ││ │
22 (C): │ ││ │
23 (C): │ ││ │
24 (C): │ ││ │
25 (C): │ ││ │
26 (C): │ ││ │
27 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
28 (C): ┌ Pane #3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
29 (C): │ │
30 (C): │ │
31 (C): │ │
32 (C): │ │
33 (C): │ │
34 (C): │ │
35 (C): │ │
36 (C): │ │
37 (C): │ │
38 (C): │ │
39 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3064
expression: snapshot
---
00 (C): ┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ──────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └──────────────────────────────────────────────────────────┘└───────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3022
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): ┌ Pane #2 ─────────────────────────────────────────────────┐ ┌ Pane #3 ─────────────────────────────────────────────────┐
06 (C): │ │ │ │
07 (C): │ │ │ │
08 (C): │ │ │ │
09 (C): │ │ │ │
10 (C): │ │ │ │
11 (C): │ │ │ │
12 (C): │ │ │ │
13 (C): │ │ │ │
14 (C): └──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 2977
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
10 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
11 (C): │ │
12 (C): │ │
13 (C): │ │
14 (C): │ │
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4346
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #3 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
02 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
03 (C): │ │┌ Pane #6 ─────────────────────────────────────────────────┐
04 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
05 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): │ │
10 (C): │ │
11 (C): │ │
12 (C): │ │
13 (C): │ │
14 (C): │ │
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3536
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3590
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
02 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ │└──────────────────────────────────────────────────────────┘
19 (C): └───────────────────────────────────────────────────────────┘└ Pane #6 ─────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3699
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #3 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ │└──────────────────────────────────────────────────────────┘
18 (C): │ │└ Pane #5 ─────────────────────────────────────────────────┘
19 (C): └───────────────────────────────────────────────────────────┘└ Pane #6 ─────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3645
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ │└──────────────────────────────────────────────────────────┘
18 (C): │ │└ Pane #5 ─────────────────────────────────────────────────┘
19 (C): └───────────────────────────────────────────────────────────┘└ Pane #6 ─────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4574
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
02 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
03 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4517
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #3 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ │└──────────────────────────────────────────────────────────┘
09 (C): └───────────────────────────────────────────────────────────┘└ Pane #6 ─────────────────────────────────────────────────┘
10 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
11 (C): │ │
12 (C): │ │
13 (C): │ │
14 (C): │ │
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4195
expression: snapshot
---
00 (C): ┌ Pane #1 ───────────────────────────────────────┐┌ Pane #2 ───────────────────────────────────────┐
01 (C): │ │┌ Pane #3 ───────────────────────────────────────┐
02 (C): │ │┌ Pane #4 ───────────────────────────────────────┐
03 (C): │ │┌ Pane #5 ───────────────────────────────────────┐
04 (C): │ │┌ Pane #6 ───────────────────────────────────────┐
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): └────────────────────────────────────────────────┘└────────────────────────────────────────────────┘
10 (C):
11 (C):
12 (C):
13 (C):
14 (C):
15 (C):
16 (C):
17 (C):
18 (C):
19 (C):

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5402
expression: snapshot
---
00 (C): ┌ status-bar ──────────────────────────────────────────────┐─────────────────────────────────────────────────┐──────────┐
01 (C): │I am a │b bar │ │
02 (C): │status bar │ │ │
03 (C): │ │ │ │
04 (C): │ │ │ │
05 (C): │ │ │ │
06 (C): │ │ │ │
07 (C): │ │ │ │
08 (C): │ │ │ │
09 (C): └──────────────────────────────────────────────────────────┘─────────────────────────────────────────────────┘ │
10 (C): ┌ command2 ───────────────────│ ┌ command1 ────────────────────────────────────────────────┐ │
11 (C): │ │ │ │ │
12 (C): │ │ │ │ │
13 (C): │ │ │ │ │
14 (C): │ Waiting to ru└───────────────────│ Waiting to run: command1 │ │
15 (C): │ │ │ │
16 (C): │ <ENTER> to run, <Ctrl-c> to exit │ <ENTER> to run, <Ctrl-c> to exit │ │
17 (C): │ │ │ │
18 (C): │ │ │ │
19 (C): └─────────────────────────────────────────────────└──────────────────────────────────────────────────────────┘──────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4628
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #3 ─────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ │└──────────────────────────────────────────────────────────┘
07 (C): │ │└ Pane #4 ─────────────────────────────────────────────────┘
08 (C): │ │└ Pane #5 ─────────────────────────────────────────────────┘
09 (C): └───────────────────────────────────────────────────────────┘└ Pane #6 ─────────────────────────────────────────────────┘
10 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
11 (C): │ │
12 (C): │ │
13 (C): │ │
14 (C): │ │
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4399
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #3 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ │└──────────────────────────────────────────────────────────┘
08 (C): │ │└ Pane #5 ─────────────────────────────────────────────────┘
09 (C): └───────────────────────────────────────────────────────────┘└ Pane #6 ─────────────────────────────────────────────────┘
10 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
11 (C): │ │
12 (C): │ │
13 (C): │ │
14 (C): │ │
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4454
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #3 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
02 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
03 (C): │ │┌ Pane #6 ─────────────────────────────────────────────────┐
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
10 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
11 (C): │ │
12 (C): │ │
13 (C): │ │
14 (C): │ │
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,46 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4178
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): │ │
10 (C): │ │
11 (C): │ │
12 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
13 (C): ┌ Pane #2 ────────────────────────────────────────────────────────┐┌ Pane #4 ───────────────────────────────────────────┐
14 (C): │ │┌ Pane #6 ───────────────────────────────────────────┐
15 (C): │ │┌ Pane #7 ───────────────────────────────────────────┐
16 (C): │ │┌ Pane #8 ───────────────────────────────────────────┐
17 (C): │ │┌ Pane #9 ───────────────────────────────────────────┐
18 (C): │ │┌ Pane #10 ──────────────────────────────────────────┐
19 (C): └─────────────────────────────────────────────────────────────────┘┌ Pane #11 ──────────────────────────────────────────┐
20 (C): ┌ Pane #3 ────────────────────────────────────────────────────────┐│ │
21 (C): │ ││ │
22 (C): │ ││ │
23 (C): │ ││ │
24 (C): │ ││ │
25 (C): │ ││ │
26 (C): └─────────────────────────────────────────────────────────────────┘└────────────────────────────────────────────────────┘
27 (C): ┌ Pane #5 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
28 (C): │ │
29 (C): │ │
30 (C): │ │
31 (C): │ │
32 (C): │ │
33 (C): │ │
34 (C): │ │
35 (C): │ │
36 (C): │ │
37 (C): │ │
38 (C): │ │
39 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,46 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3942
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): │ │
10 (C): │ │
11 (C): │ │
12 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
13 (C): ┌ Pane #2 ───────────────────────────────────────────┐┌ Pane #4 ────────────────────────────────────────────────────────┐
14 (C): │ │┌ Pane #6 ────────────────────────────────────────────────────────┐
15 (C): │ │┌ Pane #7 ────────────────────────────────────────────────────────┐
16 (C): │ │┌ Pane #8 ────────────────────────────────────────────────────────┐
17 (C): │ │┌ Pane #9 ────────────────────────────────────────────────────────┐
18 (C): │ │┌ Pane #10 ───────────────────────────────────────────────────────┐
19 (C): └────────────────────────────────────────────────────┘┌ Pane #11 ───────────────────────────────────────────────────────┐
20 (C): ┌ Pane #3 ───────────────────────────────────────────┐┌ Pane #12 ───────────────────────────────────────────────────────┐
21 (C): │ ││ │
22 (C): │ ││ │
23 (C): │ ││ │
24 (C): │ ││ │
25 (C): │ ││ │
26 (C): └────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────┘
27 (C): ┌ Pane #5 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
28 (C): │ │
29 (C): │ │
30 (C): │ │
31 (C): │ │
32 (C): │ │
33 (C): │ │
34 (C): │ │
35 (C): │ │
36 (C): │ │
37 (C): │ │
38 (C): │ │
39 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4253
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
02 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
03 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
04 (C): │ │┌ Pane #6 ─────────────────────────────────────────────────┐
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4689
expression: snapshot
---
00 (C): I am a
01 (C): status bar
02 (C): ┌ command2 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
03 (C): │ │
04 (C): │ Waiting to run: command2 │
05 (C): │ │
06 (C): │ <ENTER> to run, <Ctrl-c> to exit │
07 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
08 (C): ┌ command1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
09 (C): │ │
10 (C): │ Waiting to run: command1 │
11 (C): │ │
12 (C): │ <ENTER> to run, <Ctrl-c> to exit │
13 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
14 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
19 (C): I am a tab bar

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3492
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #6 ─────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
07 (C): ┌ Pane #2 ──────────────────────────────────────────────────┐┌ Pane #4 ─────────────────────────────────────────────────┐
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ │└──────────────────────────────────────────────────────────┘
13 (C): └───────────────────────────────────────────────────────────┘└ Pane #5 ─────────────────────────────────────────────────┘
14 (C): ┌ Pane #3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3269
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
02 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3378
expression: snapshot
---
00 (C): ┌ Pane #3 ──────────────────────────────────────────────────┐┌ Pane #7 ─────────────────────────────────────────────────┐
01 (C): ┌ Pane #2 ──────────────────────────────────────────────────┐│ │
02 (C): ┌ Pane #4 ──────────────────────────────────────────────────┐│ │
03 (C): ┌ Pane #5 ──────────────────────────────────────────────────┐│ │
04 (C): ┌ Pane #6 ──────────────────────────────────────────────────┐│ │
05 (C): ┌ Pane #8 ──────────────────────────────────────────────────┐│ │
06 (C): ┌ Pane #9 ──────────────────────────────────────────────────┐│ │
07 (C): ┌ Pane #10 ─────────────────────────────────────────────────┐│ │
08 (C): ┌ Pane #11 ─────────────────────────────────────────────────┐│ │
09 (C): ┌ Pane #12 ─────────────────────────────────────────────────┐└──────────────────────────────────────────────────────────┘
10 (C): ┌ Pane #13 ─────────────────────────────────────────────────┐┌ Pane #15 ────────────────────────────────────────────────┐
11 (C): ┌ Pane #14 ─────────────────────────────────────────────────┐│ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3324
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
02 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
03 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
04 (C): │ │┌ Pane #6 ─────────────────────────────────────────────────┐
05 (C): │ │┌ Pane #7 ─────────────────────────────────────────────────┐
06 (C): │ │┌ Pane #8 ─────────────────────────────────────────────────┐
07 (C): │ │┌ Pane #9 ─────────────────────────────────────────────────┐
08 (C): │ │┌ Pane #10 ────────────────────────────────────────────────┐
09 (C): └───────────────────────────────────────────────────────────┘┌ Pane #11 ────────────────────────────────────────────────┐
10 (C): ┌ Pane #14 ─────────────────────────────────────────────────┐┌ Pane #12 ────────────────────────────────────────────────┐
11 (C): │ │┌ Pane #13 ────────────────────────────────────────────────┐
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3434
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
07 (C): ┌ Pane #2 ──────────────────────────────────────────────────┐┌ Pane #4 ─────────────────────────────────────────────────┐
08 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
14 (C): ┌ Pane #3 ──────────────────────────────────────────────────┐┌ Pane #6 ─────────────────────────────────────────────────┐
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3222
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ │└──────────────────────────────────────────────────────────┘
19 (C): └───────────────────────────────────────────────────────────┘└ Pane #4 ─────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 3185
expression: snapshot
---
00 (C): ┌ Pane #4 ─────────────────────────────────────────────────┐┌ Pane #1 ──────────────────────────────────────────────────┐
01 (C): │ │┌ Pane #2 ──────────────────────────────────────────────────┐
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ │└───────────────────────────────────────────────────────────┘
19 (C): └──────────────────────────────────────────────────────────┘┌ Pane #3 ──────────────────────────────────────────────────┐

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5799
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ ┌ Pane #2 ────────────────────────────────────────────┐ ┌ Pane #3 ────────────────────────────────────────────┐ │
06 (C): │ │ │ │ │ │
07 (C): │ │ │ │ │ │
08 (C): │ │ │ │ │ │
09 (C): │ │ │ │ │ │
10 (C): │ │ │ │ │ │
11 (C): │ │ │ │ │ │
12 (C): │ │ │ │ │ │
13 (C): │ │ │ │ │ │
14 (C): │ └─────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────┘ │
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5799
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ ┌ Pane #3 ────────────────────────────────────────────┐ ┌ Pane #2 ────────────────────────────────────────────┐ │
02 (C): │ │ │ │ │ │
03 (C): │ │ │ │ │ │
04 (C): │ │ │ │ │ │
05 (C): │ │ │ │ │ │
06 (C): │ │ │ │ │ │
07 (C): │ │ │ │ │ │
08 (C): │ │ │ │ │ │
09 (C): │ │ │ │ │ │
10 (C): │ └─────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────┘ │
11 (C): │ ┌ Pane #4 ────────────────────────────────────────────┐ │
12 (C): │ │ │ │
13 (C): │ │ │ │
14 (C): │ │ │ │
15 (C): │ │ │ │
16 (C): │ │ │ │
17 (C): │ │ │ │
18 (C): │ │ │ │
19 (C): └─────────────────────────────└─────────────────────────────────────────────────────┘───────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5799
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): │ │
10 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────┐
11 (C): │ │ │
12 (C): │ │ │
13 (C): │ │ │
14 (C): │ │ │
15 (C): │ │ │
16 (C): │ │ │
17 (C): │ │ │
18 (C): │ │ │
19 (C): └────────────────────────────────────────────────────────────└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5099
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ │└──────────────────────────────────────────────────────────┘
10 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5099
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ │└──────────────────────────────────────────────────────────┘
07 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ │└──────────────────────────────────────────────────────────┘
14 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5099
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ │└──────────────────────────────────────────────────────────┘
05 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ │└──────────────────────────────────────────────────────────┘
10 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ │└──────────────────────────────────────────────────────────┘
15 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5099
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ │└──────────────────────────────────────────────────────────┘
05 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
10 (C): ┌ Pane #6 ──────────────────────────────────────────────────┐┌ Pane #4 ─────────────────────────────────────────────────┐
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ │└──────────────────────────────────────────────────────────┘
15 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5099
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ │└──────────────────────────────────────────────────────────┘
05 (C): │ │┌ Pane #3 ─────────────────────────────────────────────────┐
06 (C): └───────────────────────────────────────────────────────────┘│ │
07 (C): ┌ Pane #6 ──────────────────────────────────────────────────┐│ │
08 (C): │ ││ │
09 (C): │ │└──────────────────────────────────────────────────────────┘
10 (C): │ │┌ Pane #4 ─────────────────────────────────────────────────┐
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): └───────────────────────────────────────────────────────────┘│ │
14 (C): ┌ Pane #7 ──────────────────────────────────────────────────┐└──────────────────────────────────────────────────────────┘
15 (C): │ │┌ Pane #5 ─────────────────────────────────────────────────┐
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5099
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
05 (C): ┌ Pane #6 ──────────────────────────────────────────────────┐┌ Pane #3 ─────────────────────────────────────────────────┐
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
10 (C): ┌ Pane #7 ──────────────────────────────────────────────────┐┌ Pane #4 ─────────────────────────────────────────────────┐
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
15 (C): ┌ Pane #8 ──────────────────────────────────────────────────┐┌ Pane #5 ─────────────────────────────────────────────────┐
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5097
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 1371 assertion_line: 1544
expression: snapshot expression: snapshot
--- ---
00 (C): ┌ Pane #1 ────────────────────┌ ┌ ┌ Pane #4 ─────────────────────────────────── SCROLL: 0/1 ┐─────┐ 00 (C): ┌ Pane #1 ────────────────────┌ ┌ ┌ Pane #4 ─────────────────────────────────── SCROLL: 0/1 ┐─────┐

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 1801 assertion_line: 2220
expression: snapshot expression: snapshot
--- ---
00 (C): ┌ Pane #1 ────────────────────┌ EDITING SCROLLBACK ──────────────────────────────────────┐─────────┐ 00 (C): ┌ Pane #1 ────────────────────┌ EDITING SCROLLBACK ──────────────────────────────────────┐─────────┐

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 1421 assertion_line: 1594
expression: snapshot expression: snapshot
--- ---
00 (C): ┌ Pane #1 ────────────────────┌ ┌ ┌ Pane #4 ── 0 ┐ 00 (C): ┌ Pane #1 ────────────────────┌ ┌ ┌ Pane #4 ── 0 ┐

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 1475 assertion_line: 1648
expression: snapshot expression: snapshot
--- ---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ 00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 4667
expression: snapshot
---
00 (C): ┌ Pane #5 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): │ │
10 (C): │ │
11 (C): │ │
12 (C): │ │
13 (C): │ │
14 (C): │ │
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5541
expression: snapshot
---
00 (C): ┌ Pane #2 ─────────────────────────────────────────────────┐────────────────────────────────────────────────────────────┐
01 (C): │ │ │
02 (C): │ │ │
03 (C): │ │ │
04 (C): │ │ │
05 (C): │ ┌ status-bar ──────────────────────────────────────────────┐ │
06 (C): │ │I am a │ │
07 (C): │ │status bar │─┐ │
08 (C): │ │ │ │ │
09 (C): └─────────────────────────────│ │───┐ │
10 (C): │ ┌ Pane #3 ──────────│ │ │ │
11 (C): │ │ │ │ │ │
12 (C): │ │ │ │ │ │
13 (C): │ │ │ │ │ │
14 (C): │ │ └──────────────────────────────────────────────────────────┘ │ │
15 (C): │ │ │ │ │ │
16 (C): │ │ └─│ │ │
17 (C): │ │ │ │ │
18 (C): │ │ └──────────────────────────────────────────────────────────┘ │
19 (C): └─────────└──────────────────────────────────────────────────────────┘──────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5672
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────┐ │
06 (C): │ │ │ │
07 (C): │ │ ┌ Pane #3 ─────────────────────────────────────────────────┐ │
08 (C): │ │ │ │ │
09 (C): │ │ │ ┌ Pane #4 ─────────────────────────────────────────────────┐ │
10 (C): │ │ │ │ │ │
11 (C): │ │ │ │ │ │
12 (C): │ │ │ │ │ │
13 (C): │ │ │ │ │ │
14 (C): │ └─│ │ │ │
15 (C): │ │ │ │ │
16 (C): │ └─│ │ │
17 (C): │ │ │ │
18 (C): │ └──────────────────────────────────────────────────────────┘ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
assertion_line: 5614
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ ┌ command2 ────────────────────────────┐ │
03 (C): │ │ │ │
04 (C): │ │ ┌ tab-bar ─────────────────────────────┐ │
05 (C): │ │ │I am a tab bar ┌ status-bar ──────────────────────────────────────────────┐ │
06 (C): │ │ │ │I am a │ │
07 (C): │ └─│ │status bar │─┐ │
08 (C): │ │ │ │ │ │
09 (C): │ └─────────────────────────│ │───┐ │
10 (C): │ │ │ │ │
11 (C): │ │ │ │ │
12 (C): │ │ │ │ │
13 (C): │ │ │ │ │
14 (C): │ └──────────────────────────────────────────────────────────┘ │ │
15 (C): │ │ │ <ENTER> to run, <Ctrl-c> to exit │ │
16 (C): │ └─│ │ │
17 (C): │ │ │ │
18 (C): │ └──────────────────────────────────────────────────────────┘ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Some files were not shown because too many files have changed in this diff Show more