From a6d6c0e4ff03c35cafb6f04363ecab856ee3682d Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 5 Jul 2024 15:13:51 +0200 Subject: [PATCH] feat(ui): status bar redesign (#3475) * work * work * working * get default mode from server and some ui responsiveness * work * finish design and get tests to pass * get e2e tests to pass * add classic layout * add classic layout assets * fix e2e tests * style(fmt): rustfmt * fix plugin system test * style(fmt): some cleanups --- Cargo.lock | 1 + default-plugins/status-bar/Cargo.toml | 1 + default-plugins/status-bar/src/first_line.rs | 47 +- default-plugins/status-bar/src/main.rs | 30 +- default-plugins/status-bar/src/one_line_ui.rs | 1440 +++++++++++++++++ default-plugins/tab-bar/src/line.rs | 211 ++- default-plugins/tab-bar/src/main.rs | 17 +- default-plugins/tab-bar/src/tab.rs | 2 +- src/tests/e2e/cases.rs | 132 +- src/tests/e2e/remote_runner.rs | 4 - ...j__tests__e2e__cases__bracketed_paste.snap | 6 +- ...lly_when_active_terminal_is_too_small.snap | 6 +- ...zellij__tests__e2e__cases__close_pane.snap | 6 +- ...e2e__cases__detach_and_attach_session.snap | 6 +- ...ts__e2e__cases__focus_pane_with_mouse.snap | 6 +- .../zellij__tests__e2e__cases__lock_mode.snap | 6 +- ...ests__e2e__cases__mirrored_sessions-2.snap | 6 +- ..._tests__e2e__cases__mirrored_sessions.snap | 6 +- ...__tests__e2e__cases__move_tab_to_left.snap | 6 +- ...ove_tab_to_left_until_it_wraps_around.snap | 5 +- ..._tests__e2e__cases__move_tab_to_right.snap | 6 +- ...ve_tab_to_right_until_it_wraps_around.snap | 5 +- ...ers_in_different_panes_and_same_tab-2.snap | 6 +- ...users_in_different_panes_and_same_tab.snap | 6 +- ...s__multiple_users_in_different_tabs-2.snap | 6 +- ...ses__multiple_users_in_different_tabs.snap | 6 +- ...multiple_users_in_same_pane_and_tab-2.snap | 6 +- ...__multiple_users_in_same_pane_and_tab.snap | 6 +- ...llij__tests__e2e__cases__open_new_tab.snap | 6 +- ...2e__cases__quit_and_resurrect_session.snap | 4 +- ...t_session_with_viewport_serialization.snap | 4 +- ...ellij__tests__e2e__cases__resize_pane.snap | 6 +- ...s__e2e__cases__resize_terminal_window.snap | 6 +- ...__e2e__cases__scrolling_inside_a_pane.snap | 10 +- ...s__scrolling_inside_a_pane_with_mouse.snap | 10 +- ...__cases__send_command_through_the_cli.snap | 8 +- ...2e__cases__split_terminals_vertically.snap | 6 +- ...e2e__cases__start_without_pane_frames.snap | 6 +- ..._e2e__cases__starts_with_one_terminal.snap | 6 +- ...__status_bar_loads_custom_keybindings.snap | 6 +- .../zellij__tests__e2e__cases__tmux_mode.snap | 6 +- ...ts__e2e__cases__toggle_floating_panes.snap | 10 +- ...s__e2e__cases__toggle_pane_fullscreen.snap | 6 +- ...__e2e__cases__typing_exit_closes_pane.snap | 6 +- ...__tests__e2e__cases__undo_rename_pane.snap | 6 +- ...j__tests__e2e__cases__undo_rename_tab.snap | 6 +- src/tests/e2e/steps.rs | 23 +- zellij-server/src/lib.rs | 12 +- zellij-server/src/plugins/mod.rs | 6 +- zellij-server/src/plugins/plugin_loader.rs | 19 +- zellij-server/src/plugins/plugin_map.rs | 2 + .../src/plugins/unit/plugin_tests.rs | 7 +- ..._tests__switch_to_mode_plugin_command.snap | 5 +- zellij-server/src/plugins/wasm_bridge.rs | 11 +- zellij-server/src/plugins/zellij_exports.rs | 1 + zellij-server/src/route.rs | 17 +- zellij-server/src/screen.rs | 1 + zellij-server/src/unit/screen_tests.rs | 4 + zellij-utils/assets/layouts/classic.kdl | 13 + zellij-utils/assets/layouts/classic.swap.kdl | 104 ++ zellij-utils/assets/layouts/default.kdl | 2 +- zellij-utils/assets/layouts/default.swap.kdl | 2 +- zellij-utils/assets/prost/api.event.rs | 2 + zellij-utils/src/data.rs | 1 + zellij-utils/src/input/actions.rs | 31 + zellij-utils/src/input/layout.rs | 17 + zellij-utils/src/input/mod.rs | 2 + zellij-utils/src/plugin_api/event.proto | 1 + zellij-utils/src/plugin_api/event.rs | 9 + zellij-utils/src/setup.rs | 14 + ...efault_config_with_no_cli_arguments-2.snap | 18 +- 71 files changed, 2208 insertions(+), 225 deletions(-) create mode 100644 default-plugins/status-bar/src/one_line_ui.rs create mode 100644 zellij-utils/assets/layouts/classic.kdl create mode 100644 zellij-utils/assets/layouts/classic.swap.kdl diff --git a/Cargo.lock b/Cargo.lock index 2803fb4c..cfb94204 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3811,6 +3811,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "unicode-width", "zellij-tile", "zellij-tile-utils", ] diff --git a/default-plugins/status-bar/Cargo.toml b/default-plugins/status-bar/Cargo.toml index a2ca3175..c1492c19 100644 --- a/default-plugins/status-bar/Cargo.toml +++ b/default-plugins/status-bar/Cargo.toml @@ -13,6 +13,7 @@ rand = "0.8.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0.30" +unicode-width = "0.1.8" zellij-tile = { path = "../../zellij-tile" } zellij-tile-utils = { path = "../../zellij-tile-utils" } diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 1fffd755..ee9b9478 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -8,14 +8,16 @@ use crate::{ }; use crate::{ColoredElements, LinePart}; -struct KeyShortcut { - mode: KeyMode, - action: KeyAction, - key: Option, +#[derive(Debug)] +pub struct KeyShortcut { + pub mode: KeyMode, + pub action: KeyAction, + pub key: Option, } -#[derive(PartialEq)] -enum KeyAction { +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum KeyAction { + Unlock, Lock, Pane, Tab, @@ -27,7 +29,8 @@ enum KeyAction { Tmux, } -enum KeyMode { +#[derive(Debug, Copy, Clone)] +pub enum KeyMode { Unselected, UnselectedAlternate, Selected, @@ -42,6 +45,7 @@ impl KeyShortcut { pub fn full_text(&self) -> String { match self.action { KeyAction::Lock => String::from("LOCK"), + KeyAction::Unlock => String::from("UNLOCK"), KeyAction::Pane => String::from("PANE"), KeyAction::Tab => String::from("TAB"), KeyAction::Resize => String::from("RESIZE"), @@ -82,6 +86,35 @@ impl KeyShortcut { }; format!("{}", key) } + pub fn get_key(&self) -> Option { + self.key.clone() + } + pub fn get_mode(&self) -> KeyMode { + self.mode + } + pub fn get_action(&self) -> KeyAction { + self.action + } + pub fn is_selected(&self) -> bool { + match self.mode { + KeyMode::Selected => true, + _ => false, + } + } + pub fn short_text(&self) -> String { + match self.action { + KeyAction::Lock => String::from("Lo"), + KeyAction::Unlock => String::from("Un"), + KeyAction::Pane => String::from("Pa"), + KeyAction::Tab => String::from("Ta"), + KeyAction::Resize => String::from("Re"), + KeyAction::Search => String::from("Se"), + KeyAction::Quit => String::from("Qu"), + KeyAction::Session => String::from("Se"), + KeyAction::Move => String::from("Mo"), + KeyAction::Tmux => String::from("Tm"), + } + } } /// Generate long mode shortcut tile. diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 6fed529f..7db9b09d 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -1,4 +1,5 @@ mod first_line; +mod one_line_ui; mod second_line; mod tip; @@ -15,6 +16,7 @@ use zellij_tile::prelude::*; use zellij_tile_utils::{palette_match, style}; use first_line::first_line; +use one_line_ui::one_line_ui; use second_line::{ floating_panes_are_visible, fullscreen_panes_to_hide, keybinds, locked_floating_panes_are_visible, locked_fullscreen_panes_to_hide, system_clipboard_error, @@ -35,6 +37,8 @@ struct State { mode_info: ModeInfo, text_copy_destination: Option, display_system_clipboard_failure: bool, + classic_ui: bool, + base_mode_is_locked: bool, } register_plugin!(State); @@ -177,9 +181,13 @@ fn color_elements(palette: Palette, different_color_alternates: bool) -> Colored } impl ZellijPlugin for State { - fn load(&mut self, _configuration: BTreeMap) { + fn load(&mut self, configuration: BTreeMap) { // TODO: Should be able to choose whether to use the cache through config. self.tip_name = get_cached_tip_name(); + self.classic_ui = configuration + .get("classic") + .map(|c| c == "true") + .unwrap_or(false); set_selectable(false); subscribe(&[ EventType::ModeUpdate, @@ -198,6 +206,7 @@ impl ZellijPlugin for State { should_render = true; } self.mode_info = mode_info; + self.base_mode_is_locked = self.mode_info.base_mode == Some(InputMode::Locked); }, Event::TabUpdate(tabs) => { if self.tabs != tabs { @@ -244,6 +253,21 @@ impl ZellijPlugin for State { "" }; + if rows == 1 && !self.classic_ui { + let active_tab = self.tabs.iter().find(|t| t.active); + print!( + "{}", + one_line_ui( + &self.mode_info, + active_tab, + cols, + separator, + self.base_mode_is_locked + ) + ); + return; + } + 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); @@ -418,10 +442,6 @@ pub fn style_key_with_modifier( let common_modifiers = get_common_modifiers(keyvec.iter().collect()); - // let modifier_str = match get_common_modifier(keyvec.iter().collect()) { - // Some(modifier) => modifier, - // None => "".to_string(), - // }; let no_common_modifier = common_modifiers.is_empty(); let modifier_str = common_modifiers .iter() diff --git a/default-plugins/status-bar/src/one_line_ui.rs b/default-plugins/status-bar/src/one_line_ui.rs new file mode 100644 index 00000000..87b1e2fd --- /dev/null +++ b/default-plugins/status-bar/src/one_line_ui.rs @@ -0,0 +1,1440 @@ +use ansi_term::{ANSIString, ANSIStrings}; +use ansi_term::{ + Color::{Fixed, RGB}, + Style, +}; +use std::collections::HashMap; +use zellij_tile::prelude::actions::Action; +use zellij_tile::prelude::*; +use zellij_tile_utils::palette_match; + +use crate::first_line::{to_char, KeyAction, KeyMode, KeyShortcut}; +use crate::{action_key, action_key_group, color_elements, MORE_MSG, TO_NORMAL}; +use crate::{ColoredElements, LinePart}; +use unicode_width::UnicodeWidthStr; + +pub fn one_line_ui( + help: &ModeInfo, + tab_info: Option<&TabInfo>, + mut max_len: usize, + separator: &str, + base_mode_is_locked: bool, +) -> LinePart { + let mut line_part_to_render = LinePart::default(); + let mut append = |line_part: &LinePart, max_len: &mut usize| { + line_part_to_render.append(line_part); + *max_len = max_len.saturating_sub(line_part.len); + }; + + render_mode_key_indicators(help, max_len, separator, base_mode_is_locked) + .map(|mode_key_indicators| append(&mode_key_indicators, &mut max_len)) + .and_then(|_| match help.mode { + InputMode::Normal | InputMode::Locked => render_secondary_info(help, tab_info, max_len) + .map(|secondary_info| append(&secondary_info, &mut max_len)), + _ => add_keygroup_separator(help, max_len) + .map(|key_group_separator| append(&key_group_separator, &mut max_len)) + .and_then(|_| keybinds(help, max_len)) + .map(|keybinds| append(&keybinds, &mut max_len)), + }); + line_part_to_render +} + +fn to_base_mode(base_mode: InputMode) -> Action { + Action::SwitchToMode(base_mode) +} + +fn base_mode_locked_mode_indicators(help: &ModeInfo) -> HashMap> { + let locked_binds = &help.get_keybinds_for_mode(InputMode::Locked); + let normal_binds = &help.get_keybinds_for_mode(InputMode::Normal); + let pane_binds = &help.get_keybinds_for_mode(InputMode::Pane); + let tab_binds = &help.get_keybinds_for_mode(InputMode::Tab); + let resize_binds = &help.get_keybinds_for_mode(InputMode::Resize); + let move_binds = &help.get_keybinds_for_mode(InputMode::Move); + let scroll_binds = &help.get_keybinds_for_mode(InputMode::Scroll); + let session_binds = &help.get_keybinds_for_mode(InputMode::Session); + HashMap::from([ + ( + InputMode::Locked, + vec![KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Unlock, + to_char(action_key( + locked_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + )], + ), + ( + InputMode::Normal, + vec![ + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Unlock, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Locked)], + )), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Pane, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Pane)], + )), + ), + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Tab, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Tab)], + )), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Resize, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Resize)], + )), + ), + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Move, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Move)], + )), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Search, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Scroll)], + )), + ), + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Session, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Session)], + )), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Quit, + to_char(action_key(normal_binds, &[Action::Quit])), + ), + ], + ), + ( + InputMode::Pane, + vec![ + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Unlock, + to_char(action_key( + pane_binds, + &[Action::SwitchToMode(InputMode::Locked)], + )), + ), + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Pane, + to_char(action_key( + pane_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + ), + ], + ), + ( + InputMode::Tab, + vec![ + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Unlock, + to_char(action_key( + tab_binds, + &[Action::SwitchToMode(InputMode::Locked)], + )), + ), + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Tab, + to_char(action_key( + tab_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + ), + ], + ), + ( + InputMode::Resize, + vec![ + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Unlock, + to_char(action_key( + resize_binds, + &[Action::SwitchToMode(InputMode::Locked)], + )), + ), + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Resize, + to_char(action_key( + resize_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + ), + ], + ), + ( + InputMode::Move, + vec![ + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Unlock, + to_char(action_key( + move_binds, + &[Action::SwitchToMode(InputMode::Locked)], + )), + ), + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Move, + to_char(action_key( + move_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + ), + ], + ), + ( + InputMode::Scroll, + vec![ + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Unlock, + to_char(action_key( + scroll_binds, + &[Action::SwitchToMode(InputMode::Locked)], + )), + ), + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Search, + to_char(action_key( + scroll_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + ), + ], + ), + ( + InputMode::Session, + vec![ + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Unlock, + to_char(action_key( + session_binds, + &[Action::SwitchToMode(InputMode::Locked)], + )), + ), + KeyShortcut::new( + KeyMode::Selected, + KeyAction::Session, + to_char(action_key( + session_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + ), + ], + ), + ]) +} + +fn base_mode_normal_mode_indicators(help: &ModeInfo) -> HashMap> { + let locked_binds = &help.get_keybinds_for_mode(InputMode::Locked); + let normal_binds = &help.get_keybinds_for_mode(InputMode::Normal); + let pane_binds = &help.get_keybinds_for_mode(InputMode::Pane); + let tab_binds = &help.get_keybinds_for_mode(InputMode::Tab); + let resize_binds = &help.get_keybinds_for_mode(InputMode::Resize); + let move_binds = &help.get_keybinds_for_mode(InputMode::Move); + let scroll_binds = &help.get_keybinds_for_mode(InputMode::Scroll); + let session_binds = &help.get_keybinds_for_mode(InputMode::Session); + HashMap::from([ + ( + InputMode::Locked, + vec![KeyShortcut::new( + KeyMode::Selected, + KeyAction::Lock, + to_char(action_key( + locked_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + )], + ), + ( + InputMode::Normal, + vec![ + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Lock, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Locked)], + )), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Pane, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Pane)], + )), + ), + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Tab, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Tab)], + )), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Resize, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Resize)], + )), + ), + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Move, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Move)], + )), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Search, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Scroll)], + )), + ), + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Session, + to_char(action_key( + normal_binds, + &[Action::SwitchToMode(InputMode::Session)], + )), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Quit, + to_char(action_key(normal_binds, &[Action::Quit])), + ), + ], + ), + ( + InputMode::Pane, + vec![KeyShortcut::new( + KeyMode::Selected, + KeyAction::Pane, + to_char(action_key( + pane_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + )], + ), + ( + InputMode::Tab, + vec![KeyShortcut::new( + KeyMode::Selected, + KeyAction::Tab, + to_char(action_key( + tab_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + )], + ), + ( + InputMode::Resize, + vec![KeyShortcut::new( + KeyMode::Selected, + KeyAction::Resize, + to_char(action_key( + resize_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + )], + ), + ( + InputMode::Move, + vec![KeyShortcut::new( + KeyMode::Selected, + KeyAction::Move, + to_char(action_key( + move_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + )], + ), + ( + InputMode::Scroll, + vec![KeyShortcut::new( + KeyMode::Selected, + KeyAction::Search, + to_char(action_key( + scroll_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + )], + ), + ( + InputMode::Session, + vec![KeyShortcut::new( + KeyMode::Selected, + KeyAction::Session, + to_char(action_key( + session_binds, + &[Action::SwitchToMode(InputMode::Normal)], + )), + )], + ), + ]) +} + +fn render_mode_key_indicators( + help: &ModeInfo, + max_len: usize, + separator: &str, + base_mode_is_locked: bool, +) -> Option { + let mut line_part_to_render = LinePart::default(); + let supports_arrow_fonts = !help.capabilities.arrow_fonts; + let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); + let default_keys = if base_mode_is_locked { + base_mode_locked_mode_indicators(help) + } else { + base_mode_normal_mode_indicators(help) + }; + match common_modifiers_in_all_modes(&default_keys) { + Some(modifiers) => { + if let Some(default_keys) = default_keys.get(&help.mode) { + let keys_without_common_modifiers: Vec = default_keys + .iter() + .map(|key_shortcut| { + let key = key_shortcut + .get_key() + .map(|k| k.strip_common_modifiers(&modifiers)); + let mode = key_shortcut.get_mode(); + let action = key_shortcut.get_action(); + KeyShortcut::new(mode, action, key) + }) + .collect(); + render_common_modifiers( + &colored_elements, + help, + &modifiers, + &mut line_part_to_render, + separator, + ); + + let full_shortcut_list = + full_inline_keys_modes_shortcut_list(&keys_without_common_modifiers, help); + + if line_part_to_render.len + full_shortcut_list.len <= max_len { + line_part_to_render.append(&full_shortcut_list); + } else { + let shortened_shortcut_list = shortened_inline_keys_modes_shortcut_list( + &keys_without_common_modifiers, + help, + ); + if line_part_to_render.len + shortened_shortcut_list.len <= max_len { + line_part_to_render.append(&shortened_shortcut_list); + } + } + } + }, + None => { + if let Some(default_keys) = default_keys.get(&help.mode) { + let full_shortcut_list = full_modes_shortcut_list(&default_keys, help); + if line_part_to_render.len + full_shortcut_list.len <= max_len { + line_part_to_render.append(&full_shortcut_list); + } else { + let shortened_shortcut_list = + shortened_modes_shortcut_list(&default_keys, help); + if line_part_to_render.len + shortened_shortcut_list.len <= max_len { + line_part_to_render.append(&shortened_shortcut_list); + } + } + } + }, + } + if line_part_to_render.len <= max_len { + Some(line_part_to_render) + } else { + None + } +} + +fn full_inline_keys_modes_shortcut_list( + keys_without_common_modifiers: &Vec, + help: &ModeInfo, +) -> LinePart { + let mut full_shortcut_list = LinePart::default(); + for key in keys_without_common_modifiers { + let is_selected = key.is_selected(); + let shortcut = add_shortcut_with_inline_key( + help, + &key.full_text(), + key.key + .as_ref() + .map(|k| vec![k.clone()]) + .unwrap_or_else(|| vec![]), + is_selected, + ); + full_shortcut_list.append(&shortcut); + } + full_shortcut_list +} + +fn shortened_inline_keys_modes_shortcut_list( + keys_without_common_modifiers: &Vec, + help: &ModeInfo, +) -> LinePart { + let mut shortened_shortcut_list = LinePart::default(); + for key in keys_without_common_modifiers { + let is_selected = key.is_selected(); + let shortcut = add_shortcut_with_key_only( + help, + key.key + .as_ref() + .map(|k| vec![k.clone()]) + .unwrap_or_else(|| vec![]), + is_selected, + ); + shortened_shortcut_list.append(&shortcut); + } + shortened_shortcut_list +} + +fn full_modes_shortcut_list(default_keys: &Vec, help: &ModeInfo) -> LinePart { + let mut full_shortcut_list = LinePart::default(); + for key in default_keys { + let is_selected = key.is_selected(); + full_shortcut_list.append(&add_shortcut( + help, + &key.full_text(), + &key.key + .as_ref() + .map(|k| vec![k.clone()]) + .unwrap_or_else(|| vec![]), + is_selected, + Some(3), + )); + } + full_shortcut_list +} + +fn shortened_modes_shortcut_list(default_keys: &Vec, help: &ModeInfo) -> LinePart { + let mut shortened_shortcut_list = LinePart::default(); + for key in default_keys { + let is_selected = key.is_selected(); + shortened_shortcut_list.append(&add_shortcut( + help, + &key.short_text(), + &key.key + .as_ref() + .map(|k| vec![k.clone()]) + .unwrap_or_else(|| vec![]), + is_selected, + Some(3), + )); + } + shortened_shortcut_list +} + +fn common_modifiers_in_all_modes( + key_shortcuts: &HashMap>, +) -> Option> { + let Some(mut common_modifiers) = key_shortcuts.iter().next().and_then(|k| { + k.1.iter() + .next() + .and_then(|k| k.get_key().map(|k| k.key_modifiers.clone())) + }) else { + return None; + }; + for (_mode, key_shortcuts) in key_shortcuts { + if key_shortcuts.is_empty() { + return None; + } + let Some(mut common_modifiers_for_mode) = key_shortcuts + .iter() + .next() + .unwrap() + .get_key() + .map(|k| k.key_modifiers.clone()) + else { + return None; + }; + for key in key_shortcuts { + let Some(key) = key.get_key() else { + return None; + }; + common_modifiers_for_mode = common_modifiers_for_mode + .intersection(&key.key_modifiers) + .cloned() + .collect(); + } + common_modifiers = common_modifiers + .intersection(&common_modifiers_for_mode) + .cloned() + .collect(); + } + if common_modifiers.is_empty() { + return None; + } + Some(common_modifiers.into_iter().collect()) +} + +fn render_common_modifiers( + palette: &ColoredElements, + mode_info: &ModeInfo, + common_modifiers: &Vec, + line_part_to_render: &mut LinePart, + separator: &str, +) { + let prefix_text = if mode_info.capabilities.arrow_fonts { + // Add extra space in simplified ui + format!( + " {} + ", + common_modifiers + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-") + ) + } else { + format!( + " {} +", + common_modifiers + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-") + ) + }; + + let suffix_separator = palette.superkey_suffix_separator.paint(separator); + line_part_to_render.part = format!( + "{}{}{}", + line_part_to_render.part, + serialize_text(&Text::new(&prefix_text)), + suffix_separator + ); + line_part_to_render.len += prefix_text.chars().count() + separator.chars().count(); +} + +fn render_secondary_info( + help: &ModeInfo, + tab_info: Option<&TabInfo>, + max_len: usize, +) -> Option { + let mut secondary_info = LinePart::default(); + let supports_arrow_fonts = !help.capabilities.arrow_fonts; + let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); + let secondary_keybinds = secondary_keybinds(&help, tab_info, max_len); + secondary_info.append(&secondary_keybinds); + let remaining_space = max_len.saturating_sub(secondary_info.len).saturating_sub(1); // 1 for the end padding of the line + let mut padding = String::new(); + let mut padding_len = 0; + for _ in 0..remaining_space { + padding.push_str(&ANSIStrings(&[colored_elements.superkey_prefix.paint(" ")]).to_string()); + padding_len += 1; + } + secondary_info.part = format!("{}{}", padding, secondary_info.part); + secondary_info.len += padding_len; + if secondary_info.len <= max_len { + Some(secondary_info) + } else { + None + } +} + +fn secondary_keybinds(help: &ModeInfo, tab_info: Option<&TabInfo>, max_len: usize) -> LinePart { + let mut secondary_info = LinePart::default(); + let binds = &help.get_mode_keybinds(); + // New Pane + let new_pane_action_key = action_key(binds, &[Action::NewPane(None, None)]); + let mut new_pane_key_to_display = new_pane_action_key + .iter() + .find(|k| k.is_key_with_alt_modifier(BareKey::Char('n'))) + .or_else(|| new_pane_action_key.iter().next()); + let new_pane_key_to_display = + if let Some(new_pane_key_to_display) = new_pane_key_to_display.take() { + vec![new_pane_key_to_display.clone()] + } else { + vec![] + }; + + // Move focus + let mut move_focus_shortcuts: Vec = vec![]; + + // Left + let move_focus_left_action_key = action_key(binds, &[Action::MoveFocusOrTab(Direction::Left)]); + let move_focus_left_key = move_focus_left_action_key + .iter() + .find(|k| k.is_key_with_alt_modifier(BareKey::Left)) + .or_else(|| move_focus_left_action_key.iter().next()); + if let Some(move_focus_left_key) = move_focus_left_key { + move_focus_shortcuts.push(move_focus_left_key.clone()); + } + // Down + let move_focus_left_action_key = action_key(binds, &[Action::MoveFocus(Direction::Down)]); + let move_focus_left_key = move_focus_left_action_key + .iter() + .find(|k| k.is_key_with_alt_modifier(BareKey::Down)) + .or_else(|| move_focus_left_action_key.iter().next()); + if let Some(move_focus_left_key) = move_focus_left_key { + move_focus_shortcuts.push(move_focus_left_key.clone()); + } + // Up + let move_focus_left_action_key = action_key(binds, &[Action::MoveFocus(Direction::Up)]); + let move_focus_left_key = move_focus_left_action_key + .iter() + .find(|k| k.is_key_with_alt_modifier(BareKey::Up)) + .or_else(|| move_focus_left_action_key.iter().next()); + if let Some(move_focus_left_key) = move_focus_left_key { + move_focus_shortcuts.push(move_focus_left_key.clone()); + } + // Right + let move_focus_left_action_key = action_key(binds, &[Action::MoveFocusOrTab(Direction::Right)]); + let move_focus_left_key = move_focus_left_action_key + .iter() + .find(|k| k.is_key_with_alt_modifier(BareKey::Right)) + .or_else(|| move_focus_left_action_key.iter().next()); + if let Some(move_focus_left_key) = move_focus_left_key { + move_focus_shortcuts.push(move_focus_left_key.clone()); + } + + let toggle_floating_action_key = action_key(binds, &[Action::ToggleFloatingPanes]); + let mut toggle_floating_action_key = toggle_floating_action_key + .iter() + .find(|k| k.is_key_with_alt_modifier(BareKey::Char('f'))) + .or_else(|| toggle_floating_action_key.iter().next()); + let toggle_floating_key_to_display = + if let Some(toggle_floating_key_to_display) = toggle_floating_action_key.take() { + vec![toggle_floating_key_to_display.clone()] + } else { + vec![] + }; + let are_floating_panes_visible = tab_info + .map(|t| t.are_floating_panes_visible) + .unwrap_or(false); + + let common_modifiers = get_common_modifiers( + [ + new_pane_key_to_display.clone(), + move_focus_shortcuts.clone(), + toggle_floating_key_to_display.clone(), + ] + .iter() + .flatten() + .collect(), + ); + let no_common_modifier = common_modifiers.is_empty(); + + if no_common_modifier { + secondary_info.append(&add_shortcut( + help, + "New Pane", + &new_pane_key_to_display, + false, + Some(0), + )); + secondary_info.append(&add_shortcut( + help, + "Change Focus", + &move_focus_shortcuts, + false, + Some(0), + )); + secondary_info.append(&add_shortcut( + help, + "Floating", + &toggle_floating_key_to_display, + are_floating_panes_visible, + Some(0), + )); + } else { + let modifier_str = text_as_line_part_with_emphasis( + format!( + "{} + ", + common_modifiers + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-") + ), + 0, + ); + secondary_info.append(&modifier_str); + let new_pane_key_to_display: Vec = new_pane_key_to_display + .iter() + .map(|k| k.strip_common_modifiers(&common_modifiers)) + .collect(); + let move_focus_shortcuts: Vec = move_focus_shortcuts + .iter() + .map(|k| k.strip_common_modifiers(&common_modifiers)) + .collect(); + let toggle_floating_key_to_display: Vec = toggle_floating_key_to_display + .iter() + .map(|k| k.strip_common_modifiers(&common_modifiers)) + .collect(); + secondary_info.append(&add_shortcut_with_inline_key( + help, + "New Pane", + new_pane_key_to_display, + false, + )); + secondary_info.append(&add_shortcut_with_inline_key( + help, + "Change Focus", + move_focus_shortcuts, + false, + )); + secondary_info.append(&add_shortcut_with_inline_key( + help, + "Floating", + toggle_floating_key_to_display, + are_floating_panes_visible, + )); + } + + if secondary_info.len <= max_len { + secondary_info + } else { + let mut short_line = LinePart::default(); + if no_common_modifier { + short_line.append(&add_shortcut( + help, + "New", + &new_pane_key_to_display, + false, + Some(0), + )); + short_line.append(&add_shortcut( + help, + "Focus", + &move_focus_shortcuts, + false, + Some(0), + )); + short_line.append(&add_shortcut( + help, + "Floating", + &toggle_floating_key_to_display, + are_floating_panes_visible, + Some(0), + )); + } else { + let modifier_str = text_as_line_part_with_emphasis( + format!( + "{} + ", + common_modifiers + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-") + ), + 0, + ); + short_line.append(&modifier_str); + let new_pane_key_to_display: Vec = new_pane_key_to_display + .iter() + .map(|k| k.strip_common_modifiers(&common_modifiers)) + .collect(); + let move_focus_shortcuts: Vec = move_focus_shortcuts + .iter() + .map(|k| k.strip_common_modifiers(&common_modifiers)) + .collect(); + let toggle_floating_key_to_display: Vec = + toggle_floating_key_to_display + .iter() + .map(|k| k.strip_common_modifiers(&common_modifiers)) + .collect(); + short_line.append(&add_shortcut_with_inline_key( + help, + "New", + new_pane_key_to_display, + false, + )); + short_line.append(&add_shortcut_with_inline_key( + help, + "Focus", + move_focus_shortcuts, + false, + )); + short_line.append(&add_shortcut_with_inline_key( + help, + "Floating", + toggle_floating_key_to_display, + are_floating_panes_visible, + )); + } + short_line + } +} + +fn text_as_line_part_with_emphasis(text: String, emphases_index: usize) -> LinePart { + let part = serialize_text(&Text::new(&text).color_range(emphases_index, ..)); + LinePart { + part, + len: text.width(), + } +} + +fn keybinds(help: &ModeInfo, max_width: usize) -> Option { + let full_shortcut_list = full_shortcut_list(help); + if full_shortcut_list.len <= max_width { + return Some(full_shortcut_list); + } + let shortened_shortcut_list = shortened_shortcut_list(help); + if shortened_shortcut_list.len <= max_width { + return Some(shortened_shortcut_list); + } + Some(best_effort_shortcut_list(help, max_width)) +} + +fn add_shortcut( + help: &ModeInfo, + text: &str, + keys: &Vec, + selected: bool, + key_color_index: Option, +) -> LinePart { + let mut ret = LinePart::default(); + if keys.is_empty() { + return ret; + } + + ret.append(&style_key_with_modifier(&keys, key_color_index)); // TODO: alternate + // + let ribbon = if selected { + serialize_ribbon(&Text::new(format!("{}", text)).selected()) + } else { + serialize_ribbon(&Text::new(format!("{}", text))) + }; + ret.part = format!("{}{}", ret.part, ribbon); + let supports_arrow_fonts = !help.capabilities.arrow_fonts; + ret.len += if supports_arrow_fonts { + text.width() + 4 // padding and arrow fonts + } else { + text.width() + 2 // padding + }; + ret +} + +fn add_shortcut_with_inline_key( + help: &ModeInfo, + text: &str, + key: Vec, + is_selected: bool, +) -> LinePart { + let capabilities = help.capabilities; + + let mut ret = LinePart::default(); + if key.is_empty() { + return ret; + } + + let key_separator = match key + .iter() + .map(|k| k.to_string()) + .collect::>() + .join("") + .as_str() + { + "HJKL" => "", + "hjkl" => "", + "←↓↑→" => "", + "←→" => "", + "↓↑" => "", + "[]" => "", + _ => "|", + }; + + let key_string = format!( + "{}", + key.iter() + .map(|k| k.to_string()) + .collect::>() + .join(key_separator) + ); + + let ribbon = if is_selected { + serialize_ribbon( + &Text::new(format!("<{}> {}", key_string, text)) + .color_range(0, 1..key_string.width() + 1) + .selected(), + ) + } else { + serialize_ribbon( + &Text::new(format!("<{}> {}", key_string, text)) + .color_range(0, 1..key_string.width() + 1), + ) + }; + ret.part = ribbon; + let supports_arrow_fonts = !capabilities.arrow_fonts; + ret.len += if supports_arrow_fonts { + text.width() + key_string.width() + 7 // padding, group boundaries and arrow fonts + } else { + text.width() + key_string.width() + 5 // padding and group boundaries + }; + + ret +} + +fn add_shortcut_with_key_only( + help: &ModeInfo, + key: Vec, + is_selected: bool, +) -> LinePart { + let mut ret = LinePart::default(); + if key.is_empty() { + return ret; + } + + let key_string = format!( + "{}", + key.iter() + .map(|k| k.to_string()) + .collect::>() + .join("-") + ); + + let ribbon = if is_selected { + serialize_ribbon( + &Text::new(format!("{}", key_string)) + .color_range(0, ..) + .selected(), + ) + } else { + serialize_ribbon(&Text::new(format!("{}", key_string)).color_range(0, ..)) + }; + ret.part = ribbon; + let supports_arrow_fonts = !help.capabilities.arrow_fonts; + ret.len += if supports_arrow_fonts { + key_string.width() + 4 // 4 => arrow fonts + padding + } else { + key_string.width() + 2 // 2 => padding + }; + ret +} + +fn add_keygroup_separator(help: &ModeInfo, max_len: usize) -> Option { + let supports_arrow_fonts = !help.capabilities.arrow_fonts; + let separator = if supports_arrow_fonts { + crate::ARROW_SEPARATOR + } else { + " " + }; + let palette = help.style.colors; + + let mut ret = LinePart::default(); + + let separator_color = palette_match!(palette.orange); + let bg_color = palette_match!(palette.black); + let mut bits: Vec = vec![]; + let mode_help_text = match help.mode { + InputMode::RenamePane => Some("RENAMING PANE"), + InputMode::RenameTab => Some("RENAMING TAB"), + InputMode::EnterSearch => Some("ENTERING SEARCH TERM"), + InputMode::Search => Some("SEARCHING"), + _ => None, + }; + if let Some(mode_help_text) = mode_help_text { + bits.push( + Style::new() + .fg(separator_color) + .bold() + .paint(format!(" {} ", mode_help_text)), + ); + ret.len += mode_help_text.width() + 2; // 2 => padding + } + bits.push( + Style::new() + .fg(bg_color) + .on(separator_color) + .bold() + .paint(format!("{}", separator)), + ); + bits.push( + Style::new() + .fg(separator_color) + .on(separator_color) + .bold() + .paint(format!(" ")), + ); + bits.push( + Style::new() + .fg(separator_color) + .on(bg_color) + .bold() + .paint(format!("{}", separator)), + ); + ret.part = format!("{}{}", ret.part, ANSIStrings(&bits)); + ret.len += 3; // padding and arrow fonts + + if ret.len <= max_len { + Some(ret) + } else { + None + } +} + +fn full_shortcut_list(help: &ModeInfo) -> LinePart { + match help.mode { + InputMode::Normal => LinePart::default(), + InputMode::Locked => LinePart::default(), + _ => full_shortcut_list_nonstandard_mode(help), + } +} + +fn full_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart { + let mut line_part = LinePart::default(); + let keys_and_hints = get_keys_and_hints(help); + + for (long, _short, keys) in keys_and_hints.into_iter() { + line_part.append(&add_shortcut(help, &long, &keys.to_vec(), false, Some(2))); + } + line_part +} + +#[rustfmt::skip] +fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { + use Action as A; + use InputMode as IM; + use Direction as Dir; + use actions::SearchDirection as SDir; + use actions::SearchOption as SOpt; + + let mut old_keymap = mi.get_mode_keybinds(); + let s = |string: &str| string.to_string(); + + // Find a keybinding to get back to "Normal" input mode. In this case we prefer '\n' over other + // choices. Do it here before we dedupe the keymap below! + let base_mode = mi.base_mode; + let to_basemode_keys = base_mode.map(|b| action_key(&old_keymap, &[to_base_mode(b)])).unwrap_or_else(|| action_key(&old_keymap, &[TO_NORMAL])); + let to_basemode_key = if to_basemode_keys.contains(&KeyWithModifier::new(BareKey::Enter)) { + vec![KeyWithModifier::new(BareKey::Enter)] + } else { + // Yield `vec![key]` if `to_normal_keys` has at least one key, or an empty vec otherwise. + to_basemode_keys.into_iter().take(1).collect() + }; + + // Sort and deduplicate the keybindings first. We sort after the `Key`s, and deduplicate by + // their `Action` vectors. An unstable sort is fine here because if the user maps anything to + // the same key again, anything will happen... + old_keymap.sort_unstable_by(|(keya, _), (keyb, _)| keya.partial_cmp(keyb).unwrap()); + + let mut known_actions: Vec> = vec![]; + let mut km = vec![]; + for (key, acvec) in old_keymap { + if known_actions.contains(&acvec) { + // This action is known already + continue; + } else { + known_actions.push(acvec.to_vec()); + km.push((key, acvec)); + } + } + + if mi.mode == IM::Pane { vec![ + (s("New"), s("New"), single_action_key(&km, &[A::NewPane(None, None), TO_NORMAL])), + (s("Change Focus"), s("Move"), + action_key_group(&km, &[&[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)], + &[A::MoveFocus(Dir::Up)], &[A::MoveFocus(Dir::Right)]])), + (s("Close"), s("Close"), single_action_key(&km, &[A::CloseFocus, TO_NORMAL])), + (s("Rename"), s("Rename"), + single_action_key(&km, &[A::SwitchToMode(IM::RenamePane), A::PaneNameInput(vec![0])])), + (s("Toggle Fullscreen"), s("Fullscreen"), single_action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])), + (s("Toggle Floating"), s("Floating"), + single_action_key(&km, &[A::ToggleFloatingPanes, TO_NORMAL])), + (s("Toggle Embed"), s("Embed"), single_action_key(&km, &[A::TogglePaneEmbedOrFloating, TO_NORMAL])), + (s("Select pane"), s("Select"), to_basemode_key), + ]} else if mi.mode == IM::Tab { + // With the default bindings, "Move focus" for tabs is tricky: It binds all the arrow keys + // to moving tabs focus (left/up go left, right/down go right). Since we sort the keys + // above and then dedpulicate based on the actions, we will end up with LeftArrow for + // "left" and DownArrow for "right". What we really expect is to see LeftArrow and + // RightArrow. + // FIXME: So for lack of a better idea we just check this case manually here. + let old_keymap = mi.get_mode_keybinds(); + let focus_keys_full: Vec = action_key_group(&old_keymap, + &[&[A::GoToPreviousTab], &[A::GoToNextTab]]); + let focus_keys = if focus_keys_full.contains(&KeyWithModifier::new(BareKey::Left)) + && focus_keys_full.contains(&KeyWithModifier::new(BareKey::Right)) { + vec![KeyWithModifier::new(BareKey::Left), KeyWithModifier::new(BareKey::Right)] + } else { + action_key_group(&km, &[&[A::GoToPreviousTab], &[A::GoToNextTab]]) + }; + + vec![ + (s("New"), s("New"), single_action_key(&km, &[A::NewTab(None, vec![], None, None, None), TO_NORMAL])), + (s("Change focus"), s("Move"), focus_keys), + (s("Close"), s("Close"), single_action_key(&km, &[A::CloseTab, TO_NORMAL])), + (s("Rename"), s("Rename"), + single_action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), + (s("Sync"), s("Sync"), single_action_key(&km, &[A::ToggleActiveSyncTab, TO_NORMAL])), + (s("Break pane to new tab"), s("Break out"), single_action_key(&km, &[A::BreakPane, TO_NORMAL])), + (s("Break pane left/right"), s("Break"), action_key_group(&km, &[ + &[Action::BreakPaneLeft, TO_NORMAL], + &[Action::BreakPaneRight, TO_NORMAL], + ])), + (s("Toggle"), s("Toggle"), single_action_key(&km, &[A::ToggleTab])), + (s("Select pane"), s("Select"), to_basemode_key), + ]} else if mi.mode == IM::Resize { vec![ + (s("Increase/Decrease size"), s("Increase/Decrease"), + action_key_group(&km, &[ + &[A::Resize(Resize::Increase, None)], + &[A::Resize(Resize::Decrease, None)] + ])), + (s("Increase to"), s("Increase"), action_key_group(&km, &[ + &[A::Resize(Resize::Increase, Some(Dir::Left))], + &[A::Resize(Resize::Increase, Some(Dir::Down))], + &[A::Resize(Resize::Increase, Some(Dir::Up))], + &[A::Resize(Resize::Increase, Some(Dir::Right))] + ])), + (s("Decrease from"), s("Decrease"), action_key_group(&km, &[ + &[A::Resize(Resize::Decrease, Some(Dir::Left))], + &[A::Resize(Resize::Decrease, Some(Dir::Down))], + &[A::Resize(Resize::Decrease, Some(Dir::Up))], + &[A::Resize(Resize::Decrease, Some(Dir::Right))] + ])), + (s("Select pane"), s("Select"), to_basemode_key), + ]} else if mi.mode == IM::Move { vec![ + (s("Switch Location"), s("Move"), action_key_group(&km, &[ + &[Action::MovePane(Some(Dir::Left))], &[Action::MovePane(Some(Dir::Down))], + &[Action::MovePane(Some(Dir::Up))], &[Action::MovePane(Some(Dir::Right))]])), + ]} else if mi.mode == IM::Scroll { vec![ + (s("Enter search term"), s("Search"), + action_key(&km, &[A::SwitchToMode(IM::EnterSearch), A::SearchInput(vec![0])])), + (s("Scroll"), s("Scroll"), + action_key_group(&km, &[&[Action::ScrollDown], &[Action::ScrollUp]])), + (s("Scroll page"), s("Scroll"), + action_key_group(&km, &[&[Action::PageScrollDown], &[Action::PageScrollUp]])), + (s("Scroll half page"), s("Scroll"), + action_key_group(&km, &[&[Action::HalfPageScrollDown], &[Action::HalfPageScrollUp]])), + (s("Edit scrollback in default editor"), s("Edit"), + single_action_key(&km, &[Action::EditScrollback, TO_NORMAL])), + (s("Select pane"), s("Select"), to_basemode_key), + ]} else if mi.mode == IM::EnterSearch { vec![ + (s("When done"), s("Done"), action_key(&km, &[A::SwitchToMode(IM::Search)])), + (s("Cancel"), s("Cancel"), + action_key(&km, &[A::SearchInput(vec![27]), A::SwitchToMode(IM::Scroll)])), + ]} else if mi.mode == IM::Search { vec![ + (s("Enter Search term"), s("Search"), + action_key(&km, &[A::SwitchToMode(IM::EnterSearch), A::SearchInput(vec![0])])), + (s("Scroll"), s("Scroll"), + action_key_group(&km, &[&[Action::ScrollDown], &[Action::ScrollUp]])), + (s("Scroll page"), s("Scroll"), + action_key_group(&km, &[&[Action::PageScrollDown], &[Action::PageScrollUp]])), + (s("Scroll half page"), s("Scroll"), + action_key_group(&km, &[&[Action::HalfPageScrollDown], &[Action::HalfPageScrollUp]])), + (s("Search down"), s("Down"), action_key(&km, &[A::Search(SDir::Down)])), + (s("Search up"), s("Up"), action_key(&km, &[A::Search(SDir::Up)])), + (s("Case sensitive"), s("Case"), + action_key(&km, &[A::SearchToggleOption(SOpt::CaseSensitivity)])), + (s("Wrap"), s("Wrap"), + action_key(&km, &[A::SearchToggleOption(SOpt::Wrap)])), + (s("Whole words"), s("Whole"), + action_key(&km, &[A::SearchToggleOption(SOpt::WholeWord)])), + ]} else if mi.mode == IM::Session { vec![ + (s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])), + (s("Session Manager"), s("Manager"), session_manager_key(&km)), + (s("Select pane"), s("Select"), to_basemode_key), + ]} else if mi.mode == IM::Tmux { vec![ + (s("Move focus"), s("Move"), action_key_group(&km, &[ + &[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)], + &[A::MoveFocus(Dir::Up)], &[A::MoveFocus(Dir::Right)]])), + (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("Fullscreen"), s("Fullscreen"), action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])), + (s("New tab"), s("New"), action_key(&km, &[A::NewTab(None, vec![], None, None, None), TO_NORMAL])), + (s("Rename tab"), s("Rename"), + action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), + (s("Previous Tab"), s("Previous"), action_key(&km, &[A::GoToPreviousTab, TO_NORMAL])), + (s("Next Tab"), s("Next"), action_key(&km, &[A::GoToNextTab, TO_NORMAL])), + (s("Select pane"), s("Select"), to_basemode_key), + ]} else if matches!(mi.mode, IM::RenamePane | IM::RenameTab) { vec![ + (s("When done"), s("Done"), to_basemode_key), + ]} else { vec![] } +} + +fn shortened_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart { + let mut line_part = LinePart::default(); + let keys_and_hints = get_keys_and_hints(help); + + for (_, short, keys) in keys_and_hints.into_iter() { + line_part.append(&add_shortcut(help, &short, &keys.to_vec(), false, Some(2))); + } + line_part +} + +fn shortened_shortcut_list(help: &ModeInfo) -> LinePart { + match help.mode { + InputMode::Normal => LinePart::default(), + InputMode::Locked => LinePart::default(), + _ => shortened_shortcut_list_nonstandard_mode(help), + } +} + +fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart { + let mut line_part = LinePart::default(); + let keys_and_hints = get_keys_and_hints(help); + for (_, short, keys) in keys_and_hints.into_iter() { + let shortcut = add_shortcut(help, &short, &keys.to_vec(), false, Some(2)); + if line_part.len + shortcut.len + MORE_MSG.chars().count() > max_len { + line_part.part = format!("{}{}", line_part.part, MORE_MSG); + line_part.len += MORE_MSG.chars().count(); + break; + } else { + line_part.append(&shortcut); + } + } + line_part +} + +fn single_action_key( + keymap: &[(KeyWithModifier, Vec)], + action: &[Action], +) -> Vec { + let mut matching = keymap.iter().find_map(|(key, acvec)| { + if acvec.iter().next() == action.iter().next() { + Some(key.clone()) + } else { + None + } + }); + if let Some(matching) = matching.take() { + vec![matching] + } else { + vec![] + } +} + +fn session_manager_key(keymap: &[(KeyWithModifier, Vec)]) -> Vec { + let mut matching = keymap.iter().find_map(|(key, acvec)| { + let has_match = acvec + .iter() + .find(|a| a.launches_plugin("session-manager")) + .is_some(); + if has_match { + Some(key.clone()) + } else { + None + } + }); + if let Some(matching) = matching.take() { + vec![matching] + } else { + vec![] + } +} + +fn style_key_with_modifier(keyvec: &[KeyWithModifier], color_index: Option) -> LinePart { + if keyvec.is_empty() { + return LinePart::default(); + } + + let common_modifiers = get_common_modifiers(keyvec.iter().collect()); + + let no_common_modifier = common_modifiers.is_empty(); + let modifier_str = common_modifiers + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-"); + + // Prints the keys + let key = keyvec + .iter() + .map(|key| { + if no_common_modifier || keyvec.len() == 1 { + format!("{}", key) + } else { + format!("{}", key.strip_common_modifiers(&common_modifiers)) + } + }) + .collect::>(); + + // Special handling of some pre-defined keygroups + let key_string = key.join(""); + let key_separator = match &key_string[..] { + "HJKL" => "", + "hjkl" => "", + "←↓↑→" => "", + "←→" => "", + "↓↑" => "", + "[]" => "", + _ => "|", + }; + + if no_common_modifier || key.len() == 1 { + let key_string_text = format!(" {} ", key.join(key_separator)); + let text = if let Some(color_index) = color_index { + Text::new(&key_string_text).color_range(color_index, ..) + } else { + Text::new(&key_string_text) + }; + LinePart { + part: serialize_text(&text), + len: key_string_text.width(), + } + } else { + let key_string_without_modifier = format!("{}", key.join(key_separator)); + let key_string_text = format!(" {} <{}> ", modifier_str, key_string_without_modifier); + let text = if let Some(color_index) = color_index { + Text::new(&key_string_text) + .color_range(color_index, ..modifier_str.width() + 1) + .color_range( + color_index, + modifier_str.width() + 3 + ..modifier_str.width() + 3 + key_string_without_modifier.width(), + ) + } else { + Text::new(&key_string_text) + }; + LinePart { + part: serialize_text(&text), + len: key_string_text.width(), + } + } +} + +fn get_common_modifiers(mut keyvec: Vec<&KeyWithModifier>) -> Vec { + if keyvec.is_empty() { + return vec![]; + } + let mut common_modifiers = keyvec.pop().unwrap().key_modifiers.clone(); + for key in keyvec { + common_modifiers = common_modifiers + .intersection(&key.key_modifiers) + .cloned() + .collect(); + } + common_modifiers.into_iter().collect() +} diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs index 668507a0..349981f4 100644 --- a/default-plugins/tab-bar/src/line.rs +++ b/default-plugins/tab-bar/src/line.rs @@ -1,9 +1,14 @@ use ansi_term::ANSIStrings; +use ansi_term::{ + Color::{Fixed, RGB}, + Style, +}; use unicode_width::UnicodeWidthStr; use crate::{LinePart, ARROW_SEPARATOR}; +use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::*; -use zellij_tile_utils::style; +use zellij_tile_utils::{palette_match, style}; fn get_current_title_len(current_title: &[LinePart]) -> usize { current_title.iter().map(|p| p.len).sum() @@ -224,6 +229,9 @@ pub fn tab_line( palette: Palette, capabilities: PluginCapabilities, hide_session_name: bool, + tab_info: Option<&TabInfo>, + mode_info: &ModeInfo, + hide_swap_layout_indicator: bool, ) -> Vec { let mut tabs_after_active = all_tabs.split_off(active_tab_index); let mut tabs_before_active = all_tabs; @@ -236,10 +244,26 @@ pub fn tab_line( true => tab_line_prefix(None, palette, cols), false => tab_line_prefix(session_name, palette, cols), }; - let prefix_len = get_current_title_len(&prefix); + + let mut swap_layout_indicator = if hide_swap_layout_indicator { + None + } else { + tab_info.and_then(|tab_info| { + swap_layout_status( + cols, + &tab_info.active_swap_layout_name, + tab_info.is_swap_layout_dirty, + mode_info, + !capabilities.arrow_fonts, + ) + }) + }; + + let non_tab_len = + get_current_title_len(&prefix) + swap_layout_indicator.as_ref().map(|s| s.len).unwrap_or(0); // if active tab alone won't fit in cols, don't draw any tabs - if prefix_len + active_tab.len > cols { + if non_tab_len + active_tab.len > cols { return prefix; } @@ -249,10 +273,189 @@ pub fn tab_line( &mut tabs_before_active, &mut tabs_after_active, &mut tabs_to_render, - cols.saturating_sub(prefix_len), + cols.saturating_sub(non_tab_len), palette, capabilities, ); prefix.append(&mut tabs_to_render); + + if let Some(mut swap_layout_indicator) = swap_layout_indicator.take() { + let remaining_space = cols + .saturating_sub(prefix.iter().fold(0, |len, part| len + part.len)) + .saturating_sub(swap_layout_indicator.len); + let mut padding = String::new(); + let mut padding_len = 0; + for _ in 0..remaining_space { + padding.push_str(" "); + padding_len += 1; + } + swap_layout_indicator.part = format!("{}{}", padding, swap_layout_indicator.part); + swap_layout_indicator.len += padding_len; + prefix.push(swap_layout_indicator); + } + prefix } + +fn swap_layout_status( + cols: usize, + swap_layout_name: &Option, + is_swap_layout_dirty: bool, + mode_info: &ModeInfo, + supports_arrow_fonts: bool, +) -> Option { + match swap_layout_name { + Some(swap_layout_name) => { + let mode_keybinds = mode_info.get_mode_keybinds(); + let prev_next_keys = action_key_group( + &mode_keybinds, + &[&[Action::PreviousSwapLayout], &[Action::NextSwapLayout]], + ); + let mut text = style_key_with_modifier(&prev_next_keys, Some(0)); + text.append(&ribbon_as_line_part( + &swap_layout_name.to_uppercase(), + !is_swap_layout_dirty, + supports_arrow_fonts, + )); + Some(text) + }, + None => None, + } +} + +pub fn ribbon_as_line_part(text: &str, is_selected: bool, supports_arrow_fonts: bool) -> LinePart { + let ribbon_text = if is_selected { + Text::new(text).selected() + } else { + Text::new(text) + }; + let part = serialize_ribbon(&ribbon_text); + let mut len = text.width() + 2; + if supports_arrow_fonts { + len += 2; + }; + LinePart { + part, + len, + tab_index: None, + } +} + +pub fn style_key_with_modifier(keyvec: &[KeyWithModifier], color_index: Option) -> LinePart { + if keyvec.is_empty() { + return LinePart::default(); + } + + let common_modifiers = get_common_modifiers(keyvec.iter().collect()); + + let no_common_modifier = common_modifiers.is_empty(); + let modifier_str = common_modifiers + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-"); + + // Prints the keys + let key = keyvec + .iter() + .map(|key| { + if no_common_modifier || keyvec.len() == 1 { + format!("{}", key) + } else { + format!("{}", key.strip_common_modifiers(&common_modifiers)) + } + }) + .collect::>(); + + // Special handling of some pre-defined keygroups + let key_string = key.join(""); + let key_separator = match &key_string[..] { + "HJKL" => "", + "hjkl" => "", + "←↓↑→" => "", + "←→" => "", + "↓↑" => "", + "[]" => "", + _ => "|", + }; + + if no_common_modifier || key.len() == 1 { + let key_string_text = format!(" {} ", key.join(key_separator)); + let text = if let Some(color_index) = color_index { + Text::new(&key_string_text).color_range(color_index, ..) + } else { + Text::new(&key_string_text) + }; + LinePart { + part: serialize_text(&text), + len: key_string_text.width(), + ..Default::default() + } + } else { + let key_string_without_modifier = format!("{}", key.join(key_separator)); + let key_string_text = format!(" {} <{}> ", modifier_str, key_string_without_modifier); + let text = if let Some(color_index) = color_index { + Text::new(&key_string_text) + .color_range(color_index, ..modifier_str.width() + 1) + .color_range( + color_index, + modifier_str.width() + 3 + ..modifier_str.width() + 3 + key_string_without_modifier.width(), + ) + } else { + Text::new(&key_string_text) + }; + LinePart { + part: serialize_text(&text), + len: key_string_text.width(), + ..Default::default() + } + } +} + +pub fn get_common_modifiers(mut keyvec: Vec<&KeyWithModifier>) -> Vec { + if keyvec.is_empty() { + return vec![]; + } + let mut common_modifiers = keyvec.pop().unwrap().key_modifiers.clone(); + for key in keyvec { + common_modifiers = common_modifiers + .intersection(&key.key_modifiers) + .cloned() + .collect(); + } + common_modifiers.into_iter().collect() +} + +pub fn action_key_group( + keymap: &[(KeyWithModifier, Vec)], + actions: &[&[Action]], +) -> Vec { + let mut ret = vec![]; + for action in actions { + ret.extend(action_key(keymap, action)); + } + ret +} + +pub fn action_key( + keymap: &[(KeyWithModifier, Vec)], + action: &[Action], +) -> Vec { + keymap + .iter() + .filter_map(|(key, acvec)| { + let matching = acvec + .iter() + .zip(action) + .filter(|(a, b)| a.shallow_eq(b)) + .count(); + + if matching == acvec.len() && matching == action.len() { + Some(key.clone()) + } else { + None + } + }) + .collect::>() +} diff --git a/default-plugins/tab-bar/src/main.rs b/default-plugins/tab-bar/src/main.rs index 20855825..8230467d 100644 --- a/default-plugins/tab-bar/src/main.rs +++ b/default-plugins/tab-bar/src/main.rs @@ -18,12 +18,20 @@ pub struct LinePart { tab_index: Option, } +impl LinePart { + pub fn append(&mut self, to_append: &LinePart) { + self.part.push_str(&to_append.part); + self.len += to_append.len; + } +} + #[derive(Default)] struct State { tabs: Vec, active_tab_idx: usize, mode_info: ModeInfo, tab_line: Vec, + hide_swap_layout_indication: bool, } static ARROW_SEPARATOR: &str = ""; @@ -31,7 +39,11 @@ static ARROW_SEPARATOR: &str = ""; register_plugin!(State); impl ZellijPlugin for State { - fn load(&mut self, _configuration: BTreeMap) { + fn load(&mut self, configuration: BTreeMap) { + self.hide_swap_layout_indication = configuration + .get("hide_swap_layout_indication") + .map(|s| s == "true") + .unwrap_or(false); set_selectable(false); subscribe(&[ EventType::TabUpdate, @@ -120,6 +132,9 @@ impl ZellijPlugin for State { self.mode_info.style.colors, self.mode_info.capabilities, self.mode_info.style.hide_session_name, + self.tabs.iter().find(|t| t.active), + &self.mode_info, + self.hide_swap_layout_indication, ); let output = self diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs index 4b2ae777..5044ac87 100644 --- a/default-plugins/tab-bar/src/tab.rs +++ b/default-plugins/tab-bar/src/tab.rs @@ -51,7 +51,7 @@ pub fn render_tab( let right_separator = style!(background_color, foreground_color).paint(separator); let tab_styled_text = if !focused_clients.is_empty() { let (cursor_section, extra_length) = cursors(focused_clients, palette); - tab_text_len += extra_length; + tab_text_len += extra_length + 2; // 2 for cursor_beginning and cursor_end let mut s = String::new(); let cursor_beginning = style!(foreground_color, background_color) .bold() diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index 9687d53c..eec66096 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -100,7 +100,7 @@ fn account_for_races_in_snapshot(snapshot: String) -> String { // to fix this, we should set plugins as unselectable in the layout (before they are loaded), // once that happens, we should be able to remove this hack (and adjust the snapshots for the // trailing spaces that we had to get rid of here) - let base_replace = Regex::new(r" BASE \s*\n").unwrap(); + let base_replace = Regex::new(r"Alt <\[\]>  BASE \s*\n").unwrap(); let eol_arrow_replace = Regex::new(r"\s*\n").unwrap(); let snapshot = base_replace.replace_all(&snapshot, "\n").to_string(); let snapshot = eol_arrow_replace.replace_all(&snapshot, "\n").to_string(); @@ -134,6 +134,7 @@ pub fn starts_with_one_terminal() { }, }); if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; continue; } else { break last_snapshot; @@ -175,7 +176,8 @@ pub fn split_terminals_vertically() { name: "Wait for new pane to appear", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane step_is_complete = true; } @@ -219,12 +221,7 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() { name: "Make sure only one pane appears", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(3, 2) - //two empty lines at the bottom to make sure there is no plugin output - && remote_terminal - .current_snapshot() - .ends_with(" \n ") - { + if remote_terminal.cursor_position_is(3, 2) { // ... is the truncated tip line step_is_complete = true; } @@ -272,7 +269,9 @@ pub fn scrolling_inside_a_pane() { name: "Fill terminal with text", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane remote_terminal.load_fixture("e2e/scrolling_inside_a_pane"); step_is_complete = true; @@ -284,7 +283,7 @@ pub fn scrolling_inside_a_pane() { name: "Scroll up inside pane", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 20) + if remote_terminal.cursor_position_is(63, 21) && remote_terminal.snapshot_contains("line21") { // all lines have been written to the pane @@ -301,9 +300,9 @@ pub fn scrolling_inside_a_pane() { name: "Wait for scroll to finish", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 20) + if remote_terminal.cursor_position_is(63, 21) && remote_terminal.snapshot_contains("line3 ") - && remote_terminal.snapshot_contains("SCROLL: 1/4") + && remote_terminal.snapshot_contains("SCROLL: 1/3") { // keyboard scrolls up 1 line, scrollback is 4 lines: cat command + 2 extra lines from fixture + prompt step_is_complete = true; @@ -352,7 +351,9 @@ pub fn toggle_pane_fullscreen() { name: "Change newly opened pane to be fullscreen", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane remote_terminal.send_key(&PANE_MODE); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -415,7 +416,9 @@ pub fn open_new_tab() { name: "Open new tab", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane remote_terminal.send_key(&TAB_MODE); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -431,7 +434,6 @@ pub fn open_new_tab() { instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.cursor_position_is(3, 2) - && remote_terminal.tip_appears() && remote_terminal.snapshot_contains("Tab #2") && remote_terminal.status_bar_appears() { @@ -482,7 +484,9 @@ pub fn close_tab() { name: "Open new tab", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane remote_terminal.send_key(&TAB_MODE); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -497,7 +501,6 @@ pub fn close_tab() { instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.cursor_position_is(3, 2) - && remote_terminal.tip_appears() && remote_terminal.snapshot_contains("Tab #2") && remote_terminal.status_bar_appears() { @@ -675,7 +678,9 @@ pub fn close_pane() { name: "Close pane", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane remote_terminal.send_key(&PANE_MODE); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -690,7 +695,8 @@ pub fn close_pane() { name: "Wait for pane to close", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(3, 2) && remote_terminal.status_bar_appears() + { // cursor is in the original pane step_is_complete = true; } @@ -820,7 +826,9 @@ pub fn typing_exit_closes_pane() { name: "Type exit", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { remote_terminal.send_key("e".as_bytes()); std::thread::sleep(std::time::Duration::from_millis(100)); remote_terminal.send_key("x".as_bytes()); @@ -840,8 +848,8 @@ pub fn typing_exit_closes_pane() { name: "Wait for pane to close", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - // if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() { - if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(3, 2) && remote_terminal.status_bar_appears() + { // cursor is in the original pane step_is_complete = true; } @@ -889,7 +897,9 @@ pub fn resize_pane() { name: "Resize pane", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane remote_terminal.send_key(&RESIZE_MODE); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -906,7 +916,8 @@ pub fn resize_pane() { name: "Wait for pane to be resized", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(57, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(57, 2) && remote_terminal.status_bar_appears() + { // pane has been resized step_is_complete = true; } @@ -952,7 +963,7 @@ pub fn lock_mode() { name: "Send keys that should not be intercepted by the app", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.snapshot_contains("INTERFACE LOCKED") { + if !remote_terminal.snapshot_contains("PANE") { remote_terminal.send_key(&TAB_MODE); std::thread::sleep(std::time::Duration::from_millis(100)); remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE); @@ -1017,7 +1028,9 @@ pub fn resize_terminal_window() { name: "Change terminal window size", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { // new pane has been opened and focused remote_terminal.change_size(100, 24); step_is_complete = true; @@ -1031,7 +1044,7 @@ pub fn resize_terminal_window() { instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.cursor_position_is(53, 2) - && remote_terminal.tip_appears() + && remote_terminal.status_bar_appears() && remote_terminal.snapshot_contains("Ctrl +") { // size has been changed @@ -1081,7 +1094,9 @@ pub fn detach_and_attach_session() { name: "Send some text to the active pane", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { // new pane has been opened and focused remote_terminal.send_key("I am some text".as_bytes()); step_is_complete = true; @@ -1275,7 +1290,9 @@ pub fn status_bar_loads_custom_keybindings() { name: "Wait for app to load", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(3, 2) + && remote_terminal.snapshot_contains("LOCK") + { step_is_complete = true; } step_is_complete @@ -1323,7 +1340,9 @@ fn focus_pane_with_mouse() { name: "Click left pane", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { remote_terminal.send_key(&sgr_mouse_report(Position::new(5, 2), 0)); step_is_complete = true; } @@ -1335,7 +1354,8 @@ fn focus_pane_with_mouse() { name: "Wait for left pane to be focused", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(3, 2) && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane step_is_complete = true; } @@ -1383,7 +1403,9 @@ pub fn scrolling_inside_a_pane_with_mouse() { name: "Fill terminal with text", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { remote_terminal.load_fixture("e2e/scrolling_inside_a_pane"); step_is_complete = true; } @@ -1394,7 +1416,7 @@ pub fn scrolling_inside_a_pane_with_mouse() { name: "Scroll up inside pane", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 20) + if remote_terminal.cursor_position_is(63, 21) && remote_terminal.snapshot_contains("line21") { // all lines have been written to the pane @@ -1409,11 +1431,10 @@ pub fn scrolling_inside_a_pane_with_mouse() { name: "Wait for scroll to finish", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 20) + if remote_terminal.cursor_position_is(63, 21) && remote_terminal.snapshot_contains("line1 ") - && remote_terminal.snapshot_contains("SCROLL: 3/4") + && remote_terminal.snapshot_contains("SCROLL: 3/3") { - // mouse wheel scrolls up 3 lines, scrollback is 4 lines: cat command + 2 extra lines from fixture + prompt step_is_complete = true; } step_is_complete @@ -1460,7 +1481,8 @@ pub fn start_without_pane_frames() { name: "Wait for new pane to appear", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(62, 1) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(62, 1) && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane step_is_complete = true; } @@ -1529,7 +1551,9 @@ pub fn mirrored_sessions() { name: "Open new tab (second user)", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane remote_terminal.send_key(&TAB_MODE); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -1544,7 +1568,6 @@ pub fn mirrored_sessions() { instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.cursor_position_is(3, 2) - && remote_terminal.tip_appears() && remote_terminal.snapshot_contains("Tab #2") && remote_terminal.status_bar_appears() { @@ -1559,7 +1582,7 @@ pub fn mirrored_sessions() { instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.cursor_position_is(3, 2) - && remote_terminal.tip_appears() + && remote_terminal.status_bar_appears() && remote_terminal.snapshot_contains("Tab #2") { // cursor is in the newly opened second pane @@ -1584,7 +1607,7 @@ pub fn mirrored_sessions() { }); second_runner.run_all_steps(); - if first_runner.test_timed_out || second_runner.test_timed_out { + if (first_runner.test_timed_out || second_runner.test_timed_out) && test_attempts >= 0 { test_attempts -= 1; continue; } @@ -1674,7 +1697,7 @@ pub fn multiple_users_in_same_pane_and_tab() { }); second_runner.run_all_steps(); - if first_runner.test_timed_out || second_runner.test_timed_out { + if (first_runner.test_timed_out || second_runner.test_timed_out) && test_attempts >= 0 { test_attempts -= 1; continue; } @@ -1767,7 +1790,7 @@ pub fn multiple_users_in_different_panes_and_same_tab() { }); second_runner.run_all_steps(); - if first_runner.test_timed_out || second_runner.test_timed_out { + if (first_runner.test_timed_out || second_runner.test_timed_out) && test_attempts >= 0 { test_attempts -= 1; continue; } @@ -1776,7 +1799,8 @@ pub fn multiple_users_in_different_panes_and_same_tab() { name: "take snapshot after", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane step_is_complete = true; } @@ -1790,6 +1814,7 @@ pub fn multiple_users_in_different_panes_and_same_tab() { let mut step_is_complete = false; if remote_terminal.cursor_position_is(3, 2) && remote_terminal.snapshot_contains("││$") + && remote_terminal.tab_bar_appears() { // cursor is back in the first tab step_is_complete = true; @@ -1847,7 +1872,9 @@ pub fn multiple_users_in_different_tabs() { name: "Open new tab", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(3, 2) + && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane remote_terminal.send_key(&TAB_MODE); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -1859,7 +1886,7 @@ pub fn multiple_users_in_different_tabs() { }); second_runner.run_all_steps(); - if first_runner.test_timed_out || second_runner.test_timed_out { + if (first_runner.test_timed_out || second_runner.test_timed_out) && test_attempts >= 0 { test_attempts -= 1; continue; } @@ -1869,7 +1896,6 @@ pub fn multiple_users_in_different_tabs() { instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.cursor_position_is(3, 2) - && remote_terminal.tip_appears() && remote_terminal.snapshot_contains("Tab #1 [ ]") && remote_terminal.snapshot_contains("Tab #2") && remote_terminal.status_bar_appears() @@ -1887,7 +1913,6 @@ pub fn multiple_users_in_different_tabs() { instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.cursor_position_is(3, 2) - && remote_terminal.tip_appears() && remote_terminal.snapshot_contains("Tab #2 [ ]") && remote_terminal.status_bar_appears() { @@ -2006,9 +2031,7 @@ pub fn toggle_floating_panes() { name: "Wait for new pane to appear", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(33, 7) - && remote_terminal.snapshot_contains("FLOATING PANES VISIBLE") - { + if remote_terminal.cursor_position_is(33, 8) { // cursor is in the newly opened second pane step_is_complete = true; } @@ -2057,7 +2080,8 @@ pub fn tmux_mode() { name: "Wait for new pane to appear", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(63, 2) && remote_terminal.status_bar_appears() + { // cursor is in the newly opened second pane step_is_complete = true; } @@ -2163,7 +2187,7 @@ pub fn undo_rename_tab() { instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.snapshot_contains("Tab #1") - && remote_terminal.snapshot_contains("Tip:") + && remote_terminal.snapshot_contains("LOCK") { step_is_complete = true } @@ -2220,7 +2244,7 @@ pub fn undo_rename_pane() { instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.snapshot_contains("Pane #1") - && remote_terminal.snapshot_contains("Tip:") + && remote_terminal.snapshot_contains("LOCK") { step_is_complete = true } diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index b3c61fa4..49cdf64e 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -351,10 +351,6 @@ impl RemoteTerminal { pub fn cursor_position_is(&self, x: usize, y: usize) -> bool { x == self.cursor_x && y == self.cursor_y } - pub fn tip_appears(&self) -> bool { - let snapshot = self.last_snapshot.lock().unwrap(); - snapshot.contains("Tip:") || snapshot.contains("QuickNav:") - } pub fn status_bar_appears(&self) -> bool { self.last_snapshot.lock().unwrap().contains("Ctrl +") } diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap index 55cb8d8a..77031624 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1676 +assertion_line: 1975 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap index 713fac4d..98b108ed 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 198 +assertion_line: 243 expression: last_snapshot --- Zellij @@ -20,6 +20,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────┘ - - + Ctrl + diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap index d7d6423f..8ad2ef36 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 557 +assertion_line: 707 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap index 7140fc40..705a746e 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 975 +assertion_line: 1138 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap index 2ca3fa89..173f8914 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1071 +assertion_line: 1352 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap index e6d38062..9fe1892d 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 836 +assertion_line: 985 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK  <> PANE  <> TAB  <> RESIZE  <> MOVE  <> SEARCH  <> SESSION  <> QUIT  - -- INTERFACE LOCKED -- + Ctrl + LOCK  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap index 9a9efdfc..2fac31b0 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1354 +assertion_line: 1625 expression: second_runner_snapshot --- Zellij (mirrored_sessions)  Tab #1  Tab #2  @@ -24,6 +24,6 @@ expression: second_runner_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - New / <←→> Move / Close / Rename / Sync / Break out / <[]> Break / Toggle ... + Ctrl + TAB   n  New  ←→  Move  x  Close  r  Rename  s  Sync  b  Break out  []  Break  ... diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap index 3546357c..9fb84667 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1340 +assertion_line: 1624 expression: first_runner_snapshot --- Zellij (mirrored_sessions)  Tab #1  Tab #2  @@ -24,6 +24,6 @@ expression: first_runner_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_left.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_left.snap index 2e3add78..c0d32cdd 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_left.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_left.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 531 +assertion_line: 558 expression: account_for_races_in_snapshot(last_snapshot) --- Zellij (e2e-test)  Tab #1  Tab #3  Tab #2  @@ -24,6 +24,6 @@ expression: account_for_races_in_snapshot(last_snapshot) │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_left_until_it_wraps_around.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_left_until_it_wraps_around.snap index 9479e355..b7898ffb 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_left_until_it_wraps_around.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_left_until_it_wraps_around.snap @@ -1,5 +1,6 @@ --- source: src/tests/e2e/cases.rs +assertion_line: 620 expression: account_for_races_in_snapshot(last_snapshot) --- Zellij (e2e-test)  Tab #2  Tab #1  Tab #3  @@ -23,6 +24,6 @@ expression: account_for_races_in_snapshot(last_snapshot) │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_right.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_right.snap index 2aee51c5..dfe09d2e 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_right.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_right.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 624 +assertion_line: 592 expression: account_for_races_in_snapshot(last_snapshot) --- Zellij (e2e-test)  Tab #1  Tab #3  Tab #2  @@ -24,6 +24,6 @@ expression: account_for_races_in_snapshot(last_snapshot) │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_right_until_it_wraps_around.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_right_until_it_wraps_around.snap index 9c2303a5..0baedb2a 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_right_until_it_wraps_around.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__move_tab_to_right_until_it_wraps_around.snap @@ -1,5 +1,6 @@ --- source: src/tests/e2e/cases.rs +assertion_line: 644 expression: account_for_races_in_snapshot(last_snapshot) --- Zellij (e2e-test)  Tab #3  Tab #2  Tab #1  @@ -23,6 +24,6 @@ expression: account_for_races_in_snapshot(last_snapshot) │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap index 3c404b66..daf4e132 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1523 +assertion_line: 1809 expression: second_runner_snapshot --- Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ] @@ -24,6 +24,6 @@ expression: second_runner_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap index 189c1f76..ee6b7f0c 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1522 +assertion_line: 1808 expression: first_runner_snapshot --- Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ] @@ -24,6 +24,6 @@ expression: first_runner_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap index 4cad75c0..dc5de24b 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1620 +assertion_line: 1907 expression: second_runner_snapshot --- Zellij (multiple_users_in_different_tabs)  Tab #1 [ ] Tab #2  @@ -24,6 +24,6 @@ expression: second_runner_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap index 389e4ce7..c6ef6230 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1619 +assertion_line: 1906 expression: first_runner_snapshot --- Zellij (multiple_users_in_different_tabs)  Tab #1  Tab #2 [ ] @@ -24,6 +24,6 @@ expression: first_runner_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap index 4aaf8ead..3049701b 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1431 +assertion_line: 1716 expression: second_runner_snapshot --- Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ] @@ -24,6 +24,6 @@ expression: second_runner_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap index 7bd9cdd6..e3830eb6 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1430 +assertion_line: 1718 expression: first_runner_snapshot --- Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ] @@ -24,6 +24,6 @@ expression: first_runner_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap index 279a9f48..93be29fc 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 416 +assertion_line: 452 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  Tab #2  @@ -24,6 +24,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap index 5308c782..857f417d 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1171 +assertion_line: 1191 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4  @@ -25,5 +25,5 @@ expression: last_snapshot │ └────────────────────────────────────────────────────────┘ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  BASE  (FLOATING PANES VISIBLE): Press Ctrl p, to hide. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap index 8c0477f8..dff8858b 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1232 +assertion_line: 1252 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4  @@ -25,5 +25,5 @@ expression: last_snapshot │ └────────────────────────────────────────────────────────┘ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  BASE  (FLOATING PANES VISIBLE): Press Ctrl p, to hide. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap index 76421628..79b5028a 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 765 +assertion_line: 923 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap index ef696f27..da8650ae 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 889 +assertion_line: 1050 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └────────────────────────────────────────────────┘└────────────────────────────────────────────────┘ - Ctrl + g  p  t  n  h  s  o  q  Alt + <[]> - QuickNav: Alt + / Alt + <←↓↑→> or Alt + / Alt + <+|-> + Ctrl + g  p  t  n  h  s  o  q  Alt +  New Pane  <←↓↑→> Change Focus  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap index e1d36e7c..33f36714 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap @@ -1,11 +1,12 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 305 +assertion_line: 323 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  -┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 1/4 ┐ -│$ ││line3 │ +┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 1/3 ┐ +│$ ││line2 │ +│ ││line3 │ │ ││line4 │ │ ││line5 │ │ ││line6 │ @@ -25,5 +26,4 @@ expression: last_snapshot │ ││line20 │ │ ││li█e21 │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Search / <↓↑> Scroll / Scroll / Scroll / Edit / Select + Ctrl + SEARCH   s  Search  ↓↑  Scroll  PgDn|PgUp  Scroll  d|u  Scroll  e  Edit  ENTER  Select  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap index dfbbc98f..a8ec256e 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap @@ -1,11 +1,12 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1147 +assertion_line: 1428 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  -┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 3/4 ┐ -│$ ││line1 │ +┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 3/3 ┐ +│$ ││$ cat /usr/src/zellij/fixtures/e2e/scrolling_inside_a_pane│ +│ ││line1 │ │ ││line2 │ │ ││line3 │ │ ││line4 │ @@ -25,5 +26,4 @@ expression: last_snapshot │ ││line18 │ │ ││li█e19 │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap index 4d255521..197fd571 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap @@ -1,9 +1,9 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 2175 +assertion_line: 2349 expression: last_snapshot --- - Zellij (e2e-test)  Tab #1  + Zellij (e2e-test)  Tab #1  Alt <[]>  VERTICAL  ┌ Pane #1 ────────────────────────────────────────────────────────────────┐┌ /usr/src/zellij/fixtures/append-echo-script.sh ─────────────────────────┐ │$ /usr/src/zellij/x86_64-unknown-linux-musl/release/zellij run -s -- "/us││foo │ │r/src/zellij/fixtures/append-echo-script.sh" ││foo │ @@ -24,6 +24,6 @@ expression: last_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └─────────────────────────────────────────────────────────────────────────┘└ [ EXIT CODE: 0 ] re-run, drop to shell, exit ────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Alt + <[]>  VERTICAL  - Tip: Alt + => open new pane. Alt + <←↓↑→> or Alt + => navigate between panes. Alt + <+|-> => increase/decrease pane size. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Alt +  New  <←↓↑→> Focus  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap index 39f7de54..1ca3ee0a 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 168 +assertion_line: 194 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap index b8fdc86d..2d048e02 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1326 +assertion_line: 1476 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ $ │ │ │ │ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + │ + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap index b76d31ec..a78c91c2 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 120 +assertion_line: 145 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap index a0fe5fbd..93be704b 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1266 +assertion_line: 1293 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - LOCK  PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: UNBOUND => open new pane. UNBOUND => navigate between panes. UNBOUND => increase/decrease pane size. + F1  LOCK  F2  PANE  F3  TAB  F4  RESIZE  F5  MOVE  F6  SEARCH  Alt F7  SESSION  Ctrl F8  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap index d3e715c7..dc180a12 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1772 +assertion_line: 2070 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ ││ │ │ ││ │ │ ││ │ +│ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap index 3007e299..d5231a04 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap @@ -1,14 +1,15 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1984 +assertion_line: 2027 expression: last_snapshot --- - Zellij (e2e-test)  Tab #1  + Zellij (e2e-test)  Tab #1  Alt <[]>  STAGGERED  ┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │$ │ │ │ │ │ │ │ +│ │ │ ┌ Pane #2 ─────────────────────────────────────────────────┐ │ │ │$ █ │ │ │ │ │ │ @@ -18,12 +19,11 @@ expression: last_snapshot │ │ │ │ │ │ │ │ │ │ │ │ +│ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ -│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - (FLOATING PANES VISIBLE): Press Ctrl p, to hide. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap index 94d026ae..a81f9814 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 351 +assertion_line: 386 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - (FULLSCREEN): + 1 hidden panes + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap index 2907e211..33ffd4b5 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 702 +assertion_line: 858 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap index ca77b6b9..859a9763 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1921 +assertion_line: 2240 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap index 453521e5..7fa61630 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1871 +assertion_line: 2183 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,6 +24,6 @@ expression: last_snapshot │ │ │ │ │ │ +│ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/src/tests/e2e/steps.rs b/src/tests/e2e/steps.rs index 85009e19..91cc0e5b 100644 --- a/src/tests/e2e/steps.rs +++ b/src/tests/e2e/steps.rs @@ -9,7 +9,7 @@ pub fn new_tab() -> Step { name: "Open new tab", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.tip_appears() && remote_terminal.status_bar_appears() { + if remote_terminal.status_bar_appears() { remote_terminal.send_key(&TAB_MODE); std::thread::sleep(std::time::Duration::from_millis(100)); remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE); @@ -24,9 +24,7 @@ pub fn check_second_tab_opened() -> Step { Step { name: "Check second tab opened", instruction: |remote_terminal: RemoteTerminal| -> bool { - remote_terminal.status_bar_appears() - && remote_terminal.tip_appears() - && remote_terminal.snapshot_contains("Tab #2") + remote_terminal.status_bar_appears() && remote_terminal.snapshot_contains("Tab #2") }, } } @@ -36,7 +34,7 @@ pub fn move_tab_left() -> Step { name: "Move tab left", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.tip_appears() && remote_terminal.status_bar_appears() { + if remote_terminal.status_bar_appears() { remote_terminal.send_key(&MOVE_TAB_LEFT); std::thread::sleep(std::time::Duration::from_millis(100)); step_is_complete = true; @@ -51,7 +49,6 @@ pub fn check_third_tab_moved_left() -> Step { name: "Check third tab is in the middle", instruction: |remote_terminal: RemoteTerminal| -> bool { remote_terminal.status_bar_appears() - && remote_terminal.tip_appears() && remote_terminal.snapshot_contains("Tab #1  Tab #3  Tab #2") }, } @@ -62,7 +59,7 @@ pub fn type_second_tab_content() -> Step { name: "Type second tab content", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.tip_appears() && remote_terminal.status_bar_appears() { + if remote_terminal.status_bar_appears() { remote_terminal.send_key(&SECOND_TAB_CONTENT); step_is_complete = true; } @@ -75,9 +72,7 @@ pub fn check_third_tab_opened() -> Step { Step { name: "Check third tab opened", instruction: |remote_terminal: RemoteTerminal| -> bool { - remote_terminal.status_bar_appears() - && remote_terminal.tip_appears() - && remote_terminal.snapshot_contains("Tab #3") + remote_terminal.status_bar_appears() && remote_terminal.snapshot_contains("Tab #3") }, } } @@ -87,7 +82,7 @@ pub fn switch_focus_to_left_tab() -> Step { name: "Move focus to tab on the left", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.tip_appears() && remote_terminal.status_bar_appears() { + if remote_terminal.status_bar_appears() { remote_terminal.send_key(&MOVE_FOCUS_LEFT_IN_NORMAL_MODE); step_is_complete = true; } @@ -101,7 +96,6 @@ pub fn check_focus_on_second_tab() -> Step { name: "Check focus is on the second tab", instruction: |remote_terminal: RemoteTerminal| -> bool { remote_terminal.status_bar_appears() - && remote_terminal.tip_appears() && remote_terminal.snapshot_contains("Tab #2 content") }, } @@ -112,7 +106,7 @@ pub fn move_tab_right() -> Step { name: "Move tab right", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.tip_appears() && remote_terminal.status_bar_appears() { + if remote_terminal.status_bar_appears() { remote_terminal.send_key(&MOVE_TAB_RIGHT); step_is_complete = true; } @@ -126,7 +120,6 @@ pub fn check_third_tab_moved_to_beginning() -> Step { name: "Check third tab moved to beginning", instruction: |remote_terminal: RemoteTerminal| -> bool { remote_terminal.status_bar_appears() - && remote_terminal.tip_appears() && remote_terminal.snapshot_contains("Tab #3  Tab #1  Tab #2") }, } @@ -137,7 +130,6 @@ pub fn check_third_tab_is_left_wrapped() -> Step { name: "Check third tab is in last position", instruction: |remote_terminal: RemoteTerminal| -> bool { remote_terminal.status_bar_appears() - && remote_terminal.tip_appears() && remote_terminal.snapshot_contains("Tab #2  Tab #1  Tab #3") }, } @@ -148,7 +140,6 @@ pub fn check_third_tab_is_right_wrapped() -> Step { name: "Check third tab is in last position", instruction: |remote_terminal: RemoteTerminal| -> bool { remote_terminal.status_bar_appears() - && remote_terminal.tip_appears() && remote_terminal.snapshot_contains("Tab #3  Tab #2  Tab #1") }, } diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index aaedabd7..9a3231b3 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -149,6 +149,7 @@ pub(crate) struct SessionMetaData { pub config_options: Box, pub client_keybinds: HashMap, pub client_input_modes: HashMap, + pub default_mode: InputMode, screen_thread: Option>, pty_thread: Option>, plugin_thread: Option>, @@ -565,7 +566,12 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { .send_to_plugin(PluginInstruction::AddClient(client_id)) .unwrap(); let default_mode = options.default_mode.unwrap_or_default(); - let mode_info = get_mode_info(default_mode, &attrs, session_data.capabilities); + let mode_info = get_mode_info( + default_mode, + &attrs, + session_data.capabilities, + Some(default_mode), + ); session_data .senders .send_to_screen(ScreenInstruction::ChangeMode(mode_info.clone(), client_id)) @@ -999,6 +1005,8 @@ fn init_session( .clone() .unwrap_or_else(|| get_default_shell()); + let default_mode = config_options.default_mode.unwrap_or_default(); + let pty_thread = thread::Builder::new() .name("pty".to_string()) .spawn({ @@ -1089,6 +1097,7 @@ fn init_session( client_attributes, default_shell, plugin_aliases, + default_mode, ) .fatal() } @@ -1158,6 +1167,7 @@ fn init_session( plugin_thread: Some(plugin_thread), pty_writer_thread: Some(pty_writer_thread), background_jobs_thread: Some(background_jobs_thread), + default_mode, } } diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 7b9f7550..639a389e 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -25,8 +25,8 @@ use wasm_bridge::WasmBridge; use zellij_utils::{ async_std::{channel, future::timeout, task}, data::{ - Event, EventType, MessageToPlugin, PermissionStatus, PermissionType, PipeMessage, - PipeSource, PluginCapabilities, + Event, EventType, InputMode, MessageToPlugin, PermissionStatus, PermissionType, + PipeMessage, PipeSource, PluginCapabilities, }, errors::{prelude::*, ContextType, PluginContext}, input::{ @@ -198,6 +198,7 @@ pub(crate) fn plugin_thread_main( client_attributes: ClientAttributes, default_shell: Option, plugin_aliases: Box, + default_mode: InputMode, ) -> Result<()> { info!("Wasm main thread starts"); let plugin_dir = data_dir.join("plugins/"); @@ -219,6 +220,7 @@ pub(crate) fn plugin_thread_main( default_shell, layout.clone(), layout_dir, + default_mode, ); loop { diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs index 0a839b65..6ed36c05 100644 --- a/zellij-server/src/plugins/plugin_loader.rs +++ b/zellij-server/src/plugins/plugin_loader.rs @@ -26,7 +26,7 @@ use crate::{ use zellij_utils::plugin_api::action::ProtobufPluginConfiguration; use zellij_utils::{ consts::{ZELLIJ_CACHE_DIR, ZELLIJ_SESSION_CACHE_DIR, ZELLIJ_TMP_DIR}, - data::PluginCapabilities, + data::{InputMode, PluginCapabilities}, errors::prelude::*, input::command::TerminalAction, input::layout::Layout, @@ -68,6 +68,7 @@ pub struct PluginLoader<'a> { default_shell: Option, default_layout: Box, layout_dir: Option, + default_mode: InputMode, } impl<'a> PluginLoader<'a> { @@ -87,6 +88,7 @@ impl<'a> PluginLoader<'a> { default_shell: Option, default_layout: Box, layout_dir: Option, + default_mode: InputMode, ) -> Result<()> { let err_context = || format!("failed to reload plugin {plugin_id} from memory"); let mut connected_clients: Vec = @@ -112,6 +114,7 @@ impl<'a> PluginLoader<'a> { default_shell, default_layout, layout_dir, + default_mode, )?; plugin_loader .load_module_from_memory() @@ -148,6 +151,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box, skip_cache: bool, layout_dir: Option, + default_mode: InputMode, ) -> Result<()> { let err_context = || format!("failed to start plugin {plugin_id} for client {client_id}"); let mut plugin_loader = PluginLoader::new( @@ -168,6 +172,7 @@ impl<'a> PluginLoader<'a> { default_shell, default_layout, layout_dir, + default_mode, )?; if skip_cache { plugin_loader @@ -220,6 +225,7 @@ impl<'a> PluginLoader<'a> { default_shell: Option, default_layout: Box, layout_dir: Option, + default_mode: InputMode, ) -> Result<()> { let mut new_plugins = HashSet::new(); for plugin_id in plugin_map.lock().unwrap().plugin_ids() { @@ -242,6 +248,7 @@ impl<'a> PluginLoader<'a> { default_shell.clone(), default_layout.clone(), layout_dir.clone(), + default_mode, )?; plugin_loader .load_module_from_memory() @@ -270,6 +277,7 @@ impl<'a> PluginLoader<'a> { default_shell: Option, default_layout: Box, layout_dir: Option, + default_mode: InputMode, ) -> Result<()> { let err_context = || format!("failed to reload plugin id {plugin_id}"); @@ -296,6 +304,7 @@ impl<'a> PluginLoader<'a> { default_shell, default_layout, layout_dir, + default_mode, )?; plugin_loader .compile_module() @@ -328,6 +337,7 @@ impl<'a> PluginLoader<'a> { default_shell: Option, default_layout: Box, layout_dir: Option, + default_mode: InputMode, ) -> Result { let plugin_own_data_dir = ZELLIJ_SESSION_CACHE_DIR .join(Url::from(&plugin.location).to_string()) @@ -355,6 +365,7 @@ impl<'a> PluginLoader<'a> { default_shell, default_layout, layout_dir, + default_mode, }) } pub fn new_from_existing_plugin_attributes( @@ -373,6 +384,7 @@ impl<'a> PluginLoader<'a> { default_shell: Option, default_layout: Box, layout_dir: Option, + default_mode: InputMode, ) -> Result { let err_context = || "Failed to find existing plugin"; let (running_plugin, _subscriptions, _workers) = { @@ -407,6 +419,7 @@ impl<'a> PluginLoader<'a> { default_shell, default_layout, layout_dir, + default_mode, ) } pub fn new_from_different_client_id( @@ -425,6 +438,7 @@ impl<'a> PluginLoader<'a> { default_shell: Option, default_layout: Box, layout_dir: Option, + default_mode: InputMode, ) -> Result { let err_context = || "Failed to find existing plugin"; let running_plugin = { @@ -460,6 +474,7 @@ impl<'a> PluginLoader<'a> { default_shell, default_layout, layout_dir, + default_mode, ) } pub fn load_module_from_memory(&mut self) -> Result { @@ -715,6 +730,7 @@ impl<'a> PluginLoader<'a> { self.default_shell.clone(), self.default_layout.clone(), self.layout_dir.clone(), + self.default_mode, )?; plugin_loader_for_client .load_module_from_memory() @@ -814,6 +830,7 @@ impl<'a> PluginLoader<'a> { input_pipes_to_unblock: Arc::new(Mutex::new(HashSet::new())), input_pipes_to_block: Arc::new(Mutex::new(HashSet::new())), layout_dir: self.layout_dir.clone(), + default_mode: self.default_mode.clone(), subscriptions: Arc::new(Mutex::new(HashSet::new())), stdin_pipe, stdout_pipe, diff --git a/zellij-server/src/plugins/plugin_map.rs b/zellij-server/src/plugins/plugin_map.rs index fb41c89e..c5ac6d5f 100644 --- a/zellij-server/src/plugins/plugin_map.rs +++ b/zellij-server/src/plugins/plugin_map.rs @@ -19,6 +19,7 @@ use crate::{thread_bus::ThreadSenders, ClientId}; use zellij_utils::async_channel::Sender; use zellij_utils::{ data::EventType, + data::InputMode, data::PluginCapabilities, input::command::TerminalAction, input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation}, @@ -284,6 +285,7 @@ pub struct PluginEnv { pub plugin_cwd: PathBuf, pub input_pipes_to_unblock: Arc>>, pub input_pipes_to_block: Arc>>, + pub default_mode: InputMode, pub subscriptions: Arc>, pub stdin_pipe: Arc>>, pub stdout_pipe: Arc>>, diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs index 6743d5e7..c1f4fcd5 100644 --- a/zellij-server/src/plugins/unit/plugin_tests.rs +++ b/zellij-server/src/plugins/unit/plugin_tests.rs @@ -7,7 +7,8 @@ use std::path::PathBuf; use tempfile::tempdir; use wasmtime::Engine; use zellij_utils::data::{ - BareKey, Event, KeyWithModifier, PermissionStatus, PermissionType, PluginCapabilities, + BareKey, Event, InputMode, KeyWithModifier, PermissionStatus, PermissionType, + PluginCapabilities, }; use zellij_utils::errors::ErrorContext; use zellij_utils::input::layout::{ @@ -280,6 +281,7 @@ fn create_plugin_thread( client_attributes, default_shell_action, Box::new(plugin_aliases), + InputMode::Normal, ) .expect("TEST") }) @@ -359,6 +361,7 @@ fn create_plugin_thread_with_server_receiver( client_attributes, default_shell_action, Box::new(PluginAliases::default()), + InputMode::Normal, ) .expect("TEST"); }) @@ -444,6 +447,7 @@ fn create_plugin_thread_with_pty_receiver( client_attributes, default_shell_action, Box::new(PluginAliases::default()), + InputMode::Normal, ) .expect("TEST") }) @@ -524,6 +528,7 @@ fn create_plugin_thread_with_background_jobs_receiver( client_attributes, default_shell_action, Box::new(PluginAliases::default()), + InputMode::Normal, ) .expect("TEST") }) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_to_mode_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_to_mode_plugin_command.snap index 8d699ae6..36de9362 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_to_mode_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_to_mode_plugin_command.snap @@ -1,12 +1,15 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 455 +assertion_line: 1043 expression: "format!(\"{:#?}\", switch_to_mode_event)" --- Some( ChangeMode( ModeInfo { mode: Tab, + base_mode: Some( + Normal, + ), keybinds: [], style: Style { colors: Palette { diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs index 764b757d..2abc22d3 100644 --- a/zellij-server/src/plugins/wasm_bridge.rs +++ b/zellij-server/src/plugins/wasm_bridge.rs @@ -19,7 +19,7 @@ use wasmtime::{Engine, Module}; use zellij_utils::async_channel::Sender; use zellij_utils::async_std::task::{self, JoinHandle}; use zellij_utils::consts::ZELLIJ_CACHE_DIR; -use zellij_utils::data::{PermissionStatus, PermissionType, PipeMessage, PipeSource}; +use zellij_utils::data::{InputMode, PermissionStatus, PermissionType, PipeMessage, PipeSource}; use zellij_utils::downloader::Downloader; use zellij_utils::input::permission::PermissionCache; use zellij_utils::notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, FileIdMap}; @@ -102,6 +102,7 @@ pub struct WasmBridge { HashMap>>, pending_pipes: PendingPipes, layout_dir: Option, + default_mode: InputMode, } impl WasmBridge { @@ -116,6 +117,7 @@ impl WasmBridge { default_shell: Option, default_layout: Box, layout_dir: Option, + default_mode: InputMode, ) -> Self { let plugin_map = Arc::new(Mutex::new(PluginMap::default())); let connected_clients: Arc>> = Arc::new(Mutex::new(vec![])); @@ -146,6 +148,7 @@ impl WasmBridge { cached_plugin_map: HashMap::new(), pending_pipes: Default::default(), layout_dir, + default_mode, } } pub fn load_plugin( @@ -202,6 +205,7 @@ impl WasmBridge { let default_shell = self.default_shell.clone(); let default_layout = self.default_layout.clone(); let layout_dir = self.layout_dir.clone(); + let default_mode = self.default_mode; async move { let _ = senders.send_to_background_jobs( BackgroundJob::AnimatePluginLoading(plugin_id), @@ -249,6 +253,7 @@ impl WasmBridge { default_layout, skip_cache, layout_dir, + default_mode, ) { Ok(_) => handle_plugin_successful_loading(&senders, plugin_id), Err(e) => handle_plugin_loading_failure( @@ -340,6 +345,7 @@ impl WasmBridge { let default_shell = self.default_shell.clone(); let default_layout = self.default_layout.clone(); let layout_dir = self.layout_dir.clone(); + let default_mode = self.default_mode; async move { match PluginLoader::reload_plugin( first_plugin_id, @@ -357,6 +363,7 @@ impl WasmBridge { default_shell.clone(), default_layout.clone(), layout_dir.clone(), + default_mode, ) { Ok(_) => { handle_plugin_successful_loading(&senders, first_plugin_id); @@ -382,6 +389,7 @@ impl WasmBridge { default_shell.clone(), default_layout.clone(), layout_dir.clone(), + default_mode, ) { Ok(_) => handle_plugin_successful_loading(&senders, *plugin_id), Err(e) => handle_plugin_loading_failure( @@ -434,6 +442,7 @@ impl WasmBridge { self.default_shell.clone(), self.default_layout.clone(), self.layout_dir.clone(), + self.default_mode, ) { Ok(_) => { let _ = self diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index 04569238..a517df06 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -61,6 +61,7 @@ macro_rules! apply_action { $env.default_shell.clone(), $env.default_layout.clone(), None, + $env.default_mode.clone(), ) { log::error!("{}: {:?}", $error_message(), e); } diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 28dc4387..3092310a 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -13,7 +13,7 @@ use crate::{ use uuid::Uuid; use zellij_utils::{ channels::SenderWithContext, - data::{Direction, Event, PluginCapabilities, ResizeStrategy}, + data::{Direction, Event, InputMode, PluginCapabilities, ResizeStrategy}, errors::prelude::*, input::{ actions::{Action, SearchDirection, SearchOption}, @@ -38,6 +38,7 @@ pub(crate) fn route_action( default_shell: Option, default_layout: Box, mut seen_cli_pipes: Option<&mut HashSet>, + default_mode: InputMode, ) -> Result { let mut should_break = false; let err_context = || format!("failed to route action for client {client_id}"); @@ -98,7 +99,7 @@ pub(crate) fn route_action( .send_to_plugin(PluginInstruction::Update(vec![( None, Some(client_id), - Event::ModeUpdate(get_mode_info(mode, attrs, capabilities)), + Event::ModeUpdate(get_mode_info(mode, attrs, capabilities, Some(default_mode))), )])) .with_context(err_context)?; senders @@ -106,7 +107,7 @@ pub(crate) fn route_action( .with_context(err_context)?; senders .send_to_screen(ScreenInstruction::ChangeMode( - get_mode_info(mode, attrs, capabilities), + get_mode_info(mode, attrs, capabilities, Some(default_mode)), client_id, )) .with_context(err_context)?; @@ -344,7 +345,12 @@ pub(crate) fn route_action( .send_to_plugin(PluginInstruction::Update(vec![( None, None, - Event::ModeUpdate(get_mode_info(input_mode, attrs, capabilities)), + Event::ModeUpdate(get_mode_info( + input_mode, + attrs, + capabilities, + Some(default_mode), + )), )])) .with_context(err_context)?; @@ -357,6 +363,7 @@ pub(crate) fn route_action( input_mode, attrs, capabilities, + Some(default_mode), ))) .with_context(err_context)?; }, @@ -1018,6 +1025,7 @@ pub(crate) fn route_thread_main( rlocked_sessions.default_shell.clone(), rlocked_sessions.layout.clone(), Some(&mut seen_cli_pipes), + rlocked_sessions.default_mode, )? { should_break = true; } @@ -1042,6 +1050,7 @@ pub(crate) fn route_thread_main( rlocked_sessions.default_shell.clone(), rlocked_sessions.layout.clone(), Some(&mut seen_cli_pipes), + rlocked_sessions.default_mode, )? { should_break = true; } diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 8e1e4002..e61b5d19 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -2363,6 +2363,7 @@ pub(crate) fn screen_thread_main( // ¯\_(ツ)_/¯ arrow_fonts: !arrow_fonts, }, + config_options.default_mode, ), draw_pane_frames, auto_layout, diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 1fe7af29..5f7f0a66 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -114,6 +114,7 @@ fn send_cli_action_to_server( let get_current_dir = || PathBuf::from("."); let actions = Action::actions_from_cli(cli_action, Box::new(get_current_dir), None).unwrap(); let senders = session_metadata.senders.clone(); + let default_mode = session_metadata.default_mode.clone(); let capabilities = PluginCapabilities::default(); let client_attributes = ClientAttributes::default(); let default_shell = None; @@ -129,6 +130,7 @@ fn send_cli_action_to_server( default_shell.clone(), default_layout.clone(), None, + default_mode, ) .unwrap(); } @@ -516,6 +518,7 @@ impl MockScreen { layout, client_input_modes: HashMap::new(), client_keybinds: HashMap::new(), + default_mode: self.session_metadata.default_mode.clone(), } } } @@ -575,6 +578,7 @@ impl MockScreen { layout, client_input_modes: HashMap::new(), client_keybinds: HashMap::new(), + default_mode: InputMode::Normal, }; let os_input = FakeInputOutput::default(); diff --git a/zellij-utils/assets/layouts/classic.kdl b/zellij-utils/assets/layouts/classic.kdl new file mode 100644 index 00000000..a6560d73 --- /dev/null +++ b/zellij-utils/assets/layouts/classic.kdl @@ -0,0 +1,13 @@ +layout { + pane size=1 borderless=true { + plugin location="tab-bar" { + hide_swap_layout_indication true + } + } + pane + pane size=2 borderless=true { + plugin location="status-bar" { + classic true + } + } +} diff --git a/zellij-utils/assets/layouts/classic.swap.kdl b/zellij-utils/assets/layouts/classic.swap.kdl new file mode 100644 index 00000000..ae1091cb --- /dev/null +++ b/zellij-utils/assets/layouts/classic.swap.kdl @@ -0,0 +1,104 @@ +tab_template name="ui" { + pane size=1 borderless=true { + plugin location="tab-bar" { + hide_swap_layout_indication true + } + } + children + pane size=2 borderless=true { + plugin location="status-bar" { + classic true + } + } +} + +swap_tiled_layout name="vertical" { + ui max_panes=5 { + pane split_direction="vertical" { + pane + pane { children; } + } + } + ui max_panes=8 { + pane split_direction="vertical" { + pane { children; } + pane { pane; pane; pane; pane; } + } + } + ui max_panes=12 { + pane split_direction="vertical" { + pane { children; } + pane { pane; pane; pane; pane; } + pane { pane; pane; pane; pane; } + } + } +} + +swap_tiled_layout name="horizontal" { + ui max_panes=5 { + pane + pane + } + ui max_panes=8 { + pane { + pane split_direction="vertical" { children; } + pane split_direction="vertical" { pane; pane; pane; pane; } + } + } + ui max_panes=12 { + pane { + pane split_direction="vertical" { children; } + pane split_direction="vertical" { pane; pane; pane; pane; } + pane split_direction="vertical" { pane; pane; pane; pane; } + } + } +} + +swap_tiled_layout name="stacked" { + ui min_panes=5 { + pane split_direction="vertical" { + pane + pane stacked=true { children; } + } + } +} + +swap_floating_layout name="staggered" { + floating_panes +} + +swap_floating_layout name="enlarged" { + floating_panes max_panes=10 { + pane { x "5%"; y 1; width "90%"; height "90%"; } + pane { x "5%"; y 2; width "90%"; height "90%"; } + pane { x "5%"; y 3; width "90%"; height "90%"; } + pane { x "5%"; y 4; width "90%"; height "90%"; } + pane { x "5%"; y 5; width "90%"; height "90%"; } + pane { x "5%"; y 6; width "90%"; height "90%"; } + pane { x "5%"; y 7; width "90%"; height "90%"; } + pane { x "5%"; y 8; width "90%"; height "90%"; } + pane { x "5%"; y 9; width "90%"; height "90%"; } + pane focus=true { x 10; y 10; width "90%"; height "90%"; } + } +} + +swap_floating_layout name="spread" { + floating_panes max_panes=1 { + pane {y "50%"; x "50%"; } + } + floating_panes max_panes=2 { + pane { x "1%"; y "25%"; width "45%"; } + pane { x "50%"; y "25%"; width "45%"; } + } + floating_panes max_panes=3 { + pane focus=true { y "55%"; width "45%"; height "45%"; } + pane { x "1%"; y "1%"; width "45%"; } + pane { x "50%"; y "1%"; width "45%"; } + } + floating_panes max_panes=4 { + pane { x "1%"; y "55%"; width "45%"; height "45%"; } + pane focus=true { x "50%"; y "55%"; width "45%"; height "45%"; } + pane { x "1%"; y "1%"; width "45%"; height "45%"; } + pane { x "50%"; y "1%"; width "45%"; height "45%"; } + } +} diff --git a/zellij-utils/assets/layouts/default.kdl b/zellij-utils/assets/layouts/default.kdl index 460aee8a..8ce2ddf6 100644 --- a/zellij-utils/assets/layouts/default.kdl +++ b/zellij-utils/assets/layouts/default.kdl @@ -3,7 +3,7 @@ layout { plugin location="tab-bar" } pane - pane size=2 borderless=true { + pane size=1 borderless=true { plugin location="status-bar" } } diff --git a/zellij-utils/assets/layouts/default.swap.kdl b/zellij-utils/assets/layouts/default.swap.kdl index 71a6dfe9..27475819 100644 --- a/zellij-utils/assets/layouts/default.swap.kdl +++ b/zellij-utils/assets/layouts/default.swap.kdl @@ -3,7 +3,7 @@ tab_template name="ui" { plugin location="tab-bar" } children - pane size=2 borderless=true { + pane size=1 borderless=true { plugin location="status-bar" } } diff --git a/zellij-utils/assets/prost/api.event.rs b/zellij-utils/assets/prost/api.event.rs index 2a4f8427..6277b309 100644 --- a/zellij-utils/assets/prost/api.event.rs +++ b/zellij-utils/assets/prost/api.event.rs @@ -293,6 +293,8 @@ pub struct ModeUpdatePayload { pub arrow_fonts_support: bool, #[prost(string, optional, tag = "5")] pub session_name: ::core::option::Option<::prost::alloc::string::String>, + #[prost(enumeration = "super::input_mode::InputMode", optional, tag = "6")] + pub base_mode: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 602b1942..c7ebb300 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1114,6 +1114,7 @@ pub type KeybindsVec = Vec<(InputMode, Vec<(KeyWithModifier, Vec)>)>; #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ModeInfo { pub mode: InputMode, + pub base_mode: Option, pub keybinds: KeybindsVec, pub style: Style, pub capabilities: PluginCapabilities, diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index cf2f3710..5d6d99de 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -709,6 +709,37 @@ impl Action { CliAction::ListClients => Ok(vec![Action::ListClients]), } } + pub fn launches_plugin(&self, plugin_url: &str) -> bool { + match self { + Action::LaunchPlugin(run_plugin_or_alias, ..) => { + log::info!( + "1: {:?} == {:?}", + run_plugin_or_alias.location_string(), + plugin_url + ); + eprintln!( + "1: {:?} == {:?}", + run_plugin_or_alias.location_string(), + plugin_url + ); + &run_plugin_or_alias.location_string() == plugin_url + }, + Action::LaunchOrFocusPlugin(run_plugin_or_alias, ..) => { + log::info!( + "2: {:?} == {:?}", + run_plugin_or_alias.location_string(), + plugin_url + ); + eprintln!( + "2: {:?} == {:?}", + run_plugin_or_alias.location_string(), + plugin_url + ); + &run_plugin_or_alias.location_string() == plugin_url + }, + _ => false, + } + } } impl From for Action { diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 94122d9f..82207725 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -1121,6 +1121,7 @@ impl Layout { available_layouts.push(LayoutInfo::BuiltIn("strider".to_owned())); available_layouts.push(LayoutInfo::BuiltIn("disable-status-bar".to_owned())); available_layouts.push(LayoutInfo::BuiltIn("compact".to_owned())); + available_layouts.push(LayoutInfo::BuiltIn("classic".to_owned())); available_layouts.sort_by(|a, b| { let a_name = a.name(); let b_name = b.name(); @@ -1359,6 +1360,14 @@ impl Layout { Self::stringified_compact_swap_from_assets()?, )), )), + Some("classic") => Ok(( + "Classic layout".into(), + Self::stringified_classic_from_assets()?, + Some(( + "Classiclayout swap".into(), + Self::stringified_classic_swap_from_assets()?, + )), + )), Some("welcome") => Ok(( "Welcome screen layout".into(), Self::stringified_welcome_from_assets()?, @@ -1395,6 +1404,14 @@ impl Layout { Ok(String::from_utf8(setup::COMPACT_BAR_SWAP_LAYOUT.to_vec())?) } + pub fn stringified_classic_from_assets() -> Result { + Ok(String::from_utf8(setup::CLASSIC_LAYOUT.to_vec())?) + } + + pub fn stringified_classic_swap_from_assets() -> Result { + Ok(String::from_utf8(setup::CLASSIC_SWAP_LAYOUT.to_vec())?) + } + pub fn stringified_welcome_from_assets() -> Result { Ok(String::from_utf8(setup::WELCOME_LAYOUT.to_vec())?) } diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index c85bebc1..dd17ac8c 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -33,12 +33,14 @@ mod not_wasm { mode: InputMode, attributes: &ClientAttributes, capabilities: PluginCapabilities, + base_mode: Option, ) -> ModeInfo { let keybinds = attributes.keybinds.to_keybinds_vec(); let session_name = envs::get_session_name().ok(); ModeInfo { mode, + base_mode, keybinds, style: attributes.style, capabilities, diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto index abc239c0..c48030aa 100644 --- a/zellij-utils/src/plugin_api/event.proto +++ b/zellij-utils/src/plugin_api/event.proto @@ -218,6 +218,7 @@ message ModeUpdatePayload { style.Style style = 3; bool arrow_fonts_support = 4; optional string session_name = 5; + optional input_mode.InputMode base_mode = 6; } message InputModeKeybinds { diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs index e8966533..1e793e66 100644 --- a/zellij-utils/src/plugin_api/event.rs +++ b/zellij-utils/src/plugin_api/event.rs @@ -813,6 +813,9 @@ impl TryFrom for ModeInfo { ProtobufInputMode::from_i32(protobuf_mode_update_payload.current_mode) .ok_or("Malformed InputMode in the ModeUpdate Event")? .try_into()?; + let base_mode: Option = protobuf_mode_update_payload + .base_mode + .and_then(|b_m| ProtobufInputMode::from_i32(b_m)?.try_into().ok()); let keybinds: Vec<(InputMode, Vec<(KeyWithModifier, Vec)>)> = protobuf_mode_update_payload .keybinds @@ -851,6 +854,7 @@ impl TryFrom for ModeInfo { style, capabilities, session_name, + base_mode, }; Ok(mode_info) } @@ -860,6 +864,9 @@ impl TryFrom for ProtobufModeUpdatePayload { type Error = &'static str; fn try_from(mode_info: ModeInfo) -> Result { let current_mode: ProtobufInputMode = mode_info.mode.try_into()?; + let base_mode: Option = mode_info + .base_mode + .and_then(|mode| ProtobufInputMode::try_from(mode).ok()); let style: ProtobufStyle = mode_info.style.try_into()?; let arrow_fonts_support: bool = mode_info.capabilities.arrow_fonts; let session_name = mode_info.session_name; @@ -893,6 +900,7 @@ impl TryFrom for ProtobufModeUpdatePayload { keybinds: protobuf_input_mode_keybinds, arrow_fonts_support, session_name, + base_mode: base_mode.map(|b_m| b_m as i32), }) } } @@ -1112,6 +1120,7 @@ fn serialize_mode_update_event_with_non_default_values() { }, capabilities: PluginCapabilities { arrow_fonts: false }, session_name: Some("my awesome test session".to_owned()), + base_mode: Some(InputMode::Locked), }); let protobuf_event: ProtobufEvent = mode_update_event.clone().try_into().unwrap(); let serialized_protobuf_event = protobuf_event.encode_to_vec(); diff --git a/zellij-utils/src/setup.rs b/zellij-utils/src/setup.rs index 780530d7..941c351b 100644 --- a/zellij-utils/src/setup.rs +++ b/zellij-utils/src/setup.rs @@ -167,6 +167,18 @@ pub const COMPACT_BAR_SWAP_LAYOUT: &[u8] = include_bytes!(concat!( "assets/layouts/compact.swap.kdl" )); +pub const CLASSIC_LAYOUT: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/", + "assets/layouts/classic.kdl" +)); + +pub const CLASSIC_SWAP_LAYOUT: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/", + "assets/layouts/classic.swap.kdl" +)); + pub const WELCOME_LAYOUT: &[u8] = include_bytes!(concat!( env!("CARGO_MANIFEST_DIR"), "/", @@ -230,6 +242,7 @@ pub fn dump_specified_layout(layout: &str) -> std::io::Result<()> { "default" => dump_asset(DEFAULT_LAYOUT), "compact" => dump_asset(COMPACT_BAR_LAYOUT), "disable-status" => dump_asset(NO_STATUS_LAYOUT), + "classic" => dump_asset(CLASSIC_LAYOUT), custom => { info!("Dump {custom} layout"); let custom = add_layout_ext(custom); @@ -256,6 +269,7 @@ pub fn dump_specified_swap_layout(swap_layout: &str) -> std::io::Result<()> { "strider" => dump_asset(STRIDER_SWAP_LAYOUT), "default" => dump_asset(DEFAULT_SWAP_LAYOUT), "compact" => dump_asset(COMPACT_BAR_SWAP_LAYOUT), + "classic" => dump_asset(CLASSIC_SWAP_LAYOUT), not_found => Err(std::io::Error::new( std::io::ErrorKind::Other, format!("Swap Layout not found for: {}", not_found), diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap index 6b6471ee..8f85ae3e 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 699 +assertion_line: 740 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -69,7 +69,7 @@ Layout { children: [], split_size: Some( Fixed( - 2, + 1, ), ), run: Some( @@ -233,7 +233,7 @@ Layout { children: [], split_size: Some( Fixed( - 2, + 1, ), ), run: Some( @@ -455,7 +455,7 @@ Layout { children: [], split_size: Some( Fixed( - 2, + 1, ), ), run: Some( @@ -758,7 +758,7 @@ Layout { children: [], split_size: Some( Fixed( - 2, + 1, ), ), run: Some( @@ -903,7 +903,7 @@ Layout { children: [], split_size: Some( Fixed( - 2, + 1, ), ), run: Some( @@ -1125,7 +1125,7 @@ Layout { children: [], split_size: Some( Fixed( - 2, + 1, ), ), run: Some( @@ -1428,7 +1428,7 @@ Layout { children: [], split_size: Some( Fixed( - 2, + 1, ), ), run: Some( @@ -1592,7 +1592,7 @@ Layout { children: [], split_size: Some( Fixed( - 2, + 1, ), ), run: Some(