zellij/default-plugins/status-bar/src/main.rs
Aram Drevekenin f1ff272b0b
feat(ui): swap layouts and stacked panes (#2167)
* relayout working with hard coded layout

* work

* refactor(layout): PaneLayout => TiledPaneLayout

* tests passing

* tests passing

* tests passing

* stacked panes and passing tests

* tests for stacked panes

* refactor(panes): stacked panes

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

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

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

* fix(stacked-panes): resize stack

* fix(stacked-panes): focus with mouse

* fix(stacked-panes): focus next pane

* fix(layout-applier): sane focus order

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

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

* fix(relayout): properly calculate display area

* fix(relayout): properly calculate rounding errors

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

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

* feat(swap-layouts): ui + ux

* fix(swap-layouts): include base layout

* refactor(layout): remove unused method

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

* work

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

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

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

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

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

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

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

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

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

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

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

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

* fix(compact): correct swap constraint

* fix(tests): tmux swap config shortcut

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

* feat(cli): dump swap layouts

* fix(ui): stacked panes without pane frames

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

* refactor(lint): remove unused stuff

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

* style(fmt): rustfmt

* style(fmt): rustfmt

* refactor(panes): various cleanups

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

* fix(assets): merge conflicts of binary files

* style(fmt): rustfmt

* style(clippy): no thank you!

* chore(repo): remove garbage file
2023-02-17 12:05:50 +01:00

880 lines
30 KiB
Rust

mod first_line;
mod second_line;
mod tip;
use ansi_term::{
ANSIString,
Colour::{Fixed, RGB},
Style,
};
use std::fmt::{Display, Error, Formatter};
use zellij_tile::prelude::actions::Action;
use zellij_tile::prelude::*;
use zellij_tile_utils::{palette_match, style};
use first_line::first_line;
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,
text_copied_hint,
};
use tip::utils::get_cached_tip_name;
// for more of these, copy paste from: https://en.wikipedia.org/wiki/Box-drawing_character
static ARROW_SEPARATOR: &str = "";
static MORE_MSG: &str = " ... ";
/// Shorthand for `Action::SwitchToMode(InputMode::Normal)`.
const TO_NORMAL: Action = Action::SwitchToMode(InputMode::Normal);
#[derive(Default)]
struct State {
tabs: Vec<TabInfo>,
tip_name: String,
mode_info: ModeInfo,
text_copy_destination: Option<CopyDestination>,
display_system_clipboard_failure: bool,
}
register_plugin!(State);
#[derive(Default)]
pub struct LinePart {
part: String,
len: usize,
}
impl LinePart {
pub fn append(&mut self, to_append: &LinePart) {
self.part.push_str(&to_append.part);
self.len += to_append.len;
}
}
impl Display for LinePart {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.part)
}
}
#[derive(Clone, Copy)]
pub struct ColoredElements {
pub selected: SegmentStyle,
pub unselected: SegmentStyle,
pub unselected_alternate: SegmentStyle,
pub disabled: SegmentStyle,
// superkey
pub superkey_prefix: Style,
pub superkey_suffix_separator: Style,
}
#[derive(Clone, Copy)]
pub struct SegmentStyle {
pub prefix_separator: Style,
pub char_left_separator: Style,
pub char_shortcut: Style,
pub char_right_separator: Style,
pub styled_text: Style,
pub suffix_separator: Style,
}
// I really hate this, but I can't come up with a good solution for this,
// we need different colors from palette for the default theme
// plus here we can add new sources in the future, like Theme
// that can be defined in the config perhaps
fn color_elements(palette: Palette, different_color_alternates: bool) -> ColoredElements {
let background = match palette.theme_hue {
ThemeHue::Dark => palette.black,
ThemeHue::Light => palette.white,
};
let foreground = match palette.theme_hue {
ThemeHue::Dark => palette.white,
ThemeHue::Light => palette.black,
};
let alternate_background_color = if different_color_alternates {
match palette.theme_hue {
ThemeHue::Dark => palette.white,
ThemeHue::Light => palette.black,
}
} else {
palette.fg
};
match palette.source {
PaletteSource::Default => ColoredElements {
selected: SegmentStyle {
prefix_separator: style!(background, palette.green),
char_left_separator: style!(background, palette.green).bold(),
char_shortcut: style!(palette.red, palette.green).bold(),
char_right_separator: style!(background, palette.green).bold(),
styled_text: style!(background, palette.green).bold(),
suffix_separator: style!(palette.green, background).bold(),
},
unselected: SegmentStyle {
prefix_separator: style!(background, palette.fg),
char_left_separator: style!(background, palette.fg).bold(),
char_shortcut: style!(palette.red, palette.fg).bold(),
char_right_separator: style!(background, palette.fg).bold(),
styled_text: style!(background, palette.fg).bold(),
suffix_separator: style!(palette.fg, background),
},
unselected_alternate: SegmentStyle {
prefix_separator: style!(background, alternate_background_color),
char_left_separator: style!(background, alternate_background_color).bold(),
char_shortcut: style!(palette.red, alternate_background_color).bold(),
char_right_separator: style!(background, alternate_background_color).bold(),
styled_text: style!(background, alternate_background_color).bold(),
suffix_separator: style!(alternate_background_color, background),
},
disabled: SegmentStyle {
prefix_separator: style!(background, palette.fg),
char_left_separator: style!(background, palette.fg).dimmed().italic(),
char_shortcut: style!(background, palette.fg).dimmed().italic(),
char_right_separator: style!(background, palette.fg).dimmed().italic(),
styled_text: style!(background, palette.fg).dimmed().italic(),
suffix_separator: style!(palette.fg, background),
},
superkey_prefix: style!(foreground, background).bold(),
superkey_suffix_separator: style!(background, background),
},
PaletteSource::Xresources => ColoredElements {
selected: SegmentStyle {
prefix_separator: style!(background, palette.green),
char_left_separator: style!(palette.fg, palette.green).bold(),
char_shortcut: style!(palette.red, palette.green).bold(),
char_right_separator: style!(palette.fg, palette.green).bold(),
styled_text: style!(background, palette.green).bold(),
suffix_separator: style!(palette.green, background).bold(),
},
unselected: SegmentStyle {
prefix_separator: style!(background, palette.fg),
char_left_separator: style!(background, palette.fg).bold(),
char_shortcut: style!(palette.red, palette.fg).bold(),
char_right_separator: style!(background, palette.fg).bold(),
styled_text: style!(background, palette.fg).bold(),
suffix_separator: style!(palette.fg, background),
},
unselected_alternate: SegmentStyle {
prefix_separator: style!(background, alternate_background_color),
char_left_separator: style!(background, alternate_background_color).bold(),
char_shortcut: style!(palette.red, alternate_background_color).bold(),
char_right_separator: style!(background, alternate_background_color).bold(),
styled_text: style!(background, alternate_background_color).bold(),
suffix_separator: style!(alternate_background_color, background),
},
disabled: SegmentStyle {
prefix_separator: style!(background, palette.fg),
char_left_separator: style!(background, palette.fg).dimmed(),
char_shortcut: style!(background, palette.fg).dimmed(),
char_right_separator: style!(background, palette.fg).dimmed(),
styled_text: style!(background, palette.fg).dimmed(),
suffix_separator: style!(palette.fg, background),
},
superkey_prefix: style!(background, palette.fg).bold(),
superkey_suffix_separator: style!(palette.fg, background),
},
}
}
impl ZellijPlugin for State {
fn load(&mut self) {
// TODO: Should be able to choose whether to use the cache through config.
self.tip_name = get_cached_tip_name();
set_selectable(false);
subscribe(&[
EventType::ModeUpdate,
EventType::TabUpdate,
EventType::CopyToClipboard,
EventType::InputReceived,
EventType::SystemClipboardFailure,
]);
}
fn update(&mut self, event: Event) -> bool {
let mut should_render = false;
match event {
Event::ModeUpdate(mode_info) => {
if self.mode_info != mode_info {
should_render = true;
}
self.mode_info = mode_info;
},
Event::TabUpdate(tabs) => {
if self.tabs != tabs {
should_render = true;
}
self.tabs = tabs;
},
Event::CopyToClipboard(copy_destination) => {
match self.text_copy_destination {
Some(text_copy_destination) => {
if text_copy_destination != copy_destination {
should_render = true;
}
},
None => {
should_render = true;
},
}
self.text_copy_destination = Some(copy_destination);
},
Event::SystemClipboardFailure => {
should_render = true;
self.display_system_clipboard_failure = true;
},
Event::InputReceived => {
if self.text_copy_destination.is_some()
|| self.display_system_clipboard_failure == true
{
should_render = true;
}
self.text_copy_destination = None;
self.display_system_clipboard_failure = false;
},
_ => {},
};
should_render
}
fn render(&mut self, rows: usize, cols: usize) {
let supports_arrow_fonts = !self.mode_info.capabilities.arrow_fonts;
let separator = if supports_arrow_fonts {
ARROW_SEPARATOR
} else {
""
};
let active_tab = self.tabs.iter().find(|t| t.active);
let first_line = first_line(&self.mode_info, active_tab, cols, separator);
let second_line = self.second_line(cols);
let background = match self.mode_info.style.colors.theme_hue {
ThemeHue::Dark => self.mode_info.style.colors.black,
ThemeHue::Light => self.mode_info.style.colors.white,
};
// [48;5;238m is white background, [0K is so that it fills the rest of the line
// [m is background reset, [0K is so that it clears the rest of the line
match background {
PaletteColor::Rgb((r, g, b)) => {
if rows > 1 {
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b);
} else {
if self.mode_info.mode == InputMode::Normal {
print!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b);
} else {
print!("\u{1b}[m{}\u{1b}[0K", second_line);
}
}
},
PaletteColor::EightBit(color) => {
if rows > 1 {
println!("{}\u{1b}[48;5;{}m\u{1b}[0K", first_line, color);
} else {
if self.mode_info.mode == InputMode::Normal {
print!("{}\u{1b}[48;5;{}m\u{1b}[0K", first_line, color);
} else {
print!("\u{1b}[m{}\u{1b}[0K", second_line);
}
}
},
}
if rows > 1 {
print!("\u{1b}[m{}\u{1b}[0K", second_line);
}
}
}
impl State {
fn second_line(&self, cols: usize) -> LinePart {
let active_tab = self.tabs.iter().find(|t| t.active);
if let Some(copy_destination) = self.text_copy_destination {
text_copied_hint(&self.mode_info.style.colors, copy_destination)
} else if self.display_system_clipboard_failure {
system_clipboard_error(&self.mode_info.style.colors)
} else if let Some(active_tab) = active_tab {
if active_tab.is_fullscreen_active {
match self.mode_info.mode {
InputMode::Normal => fullscreen_panes_to_hide(
&self.mode_info.style.colors,
active_tab.panes_to_hide,
),
InputMode::Locked => locked_fullscreen_panes_to_hide(
&self.mode_info.style.colors,
active_tab.panes_to_hide,
),
_ => keybinds(&self.mode_info, &self.tip_name, cols),
}
} else if active_tab.are_floating_panes_visible {
match self.mode_info.mode {
InputMode::Normal => floating_panes_are_visible(&self.mode_info),
InputMode::Locked => {
locked_floating_panes_are_visible(&self.mode_info.style.colors)
},
_ => keybinds(&self.mode_info, &self.tip_name, cols),
}
} else {
keybinds(&self.mode_info, &self.tip_name, cols)
}
} else {
LinePart::default()
}
}
}
/// Get a common modifier key from a key vector.
///
/// Iterates over all keys and returns any found common modifier key. Possible modifiers that will
/// be detected are "Ctrl" and "Alt".
pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option<String> {
let mut modifier = "";
let mut new_modifier;
for key in keyvec.iter() {
match key {
Key::Ctrl(_) => new_modifier = "Ctrl",
Key::Alt(_) => new_modifier = "Alt",
_ => return None,
}
if modifier.is_empty() {
modifier = new_modifier;
} else if modifier != new_modifier {
// Prefix changed!
return None;
}
}
match modifier.is_empty() {
true => None,
false => Some(modifier.to_string()),
}
}
/// Get key from action pattern(s).
///
/// This function takes as arguments a `keymap` that is a `Vec<(Key, Vec<Action>)>` and contains
/// all keybindings for the current mode and one or more `p` patterns which match a sequence of
/// actions to search for. If within the keymap a sequence of actions matching `p` is found, all
/// keys that trigger the action pattern are returned as vector of `Vec<Key>`.
pub fn action_key(keymap: &[(Key, Vec<Action>)], action: &[Action]) -> Vec<Key> {
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)
} else {
None
}
})
.collect::<Vec<Key>>()
}
/// Get multiple keys for multiple actions.
///
/// An extension of [`action_key`] that iterates over all action tuples and collects the results.
pub fn action_key_group(keymap: &[(Key, Vec<Action>)], actions: &[&[Action]]) -> Vec<Key> {
let mut ret = vec![];
for action in actions {
ret.extend(action_key(keymap, action));
}
ret
}
/// Style a vector of [`Key`]s with the given [`Palette`].
///
/// Creates a line segment of style `<KEYS>`, with correct theming applied: The brackets have the
/// regular text color, the enclosed keys are painted green and bold. If the keys share a common
/// modifier (See [`get_common_modifier`]), it is printed in front of the keys, painted green and
/// bold, separated with a `+`: `MOD + <KEYS>`.
///
/// If multiple [`Key`]s are given, the individual keys are separated with a `|` char. This does
/// not apply to the following groups of keys which are treated specially and don't have a
/// separator between them:
///
/// - "hjkl"
/// - "HJKL"
/// - "←↓↑→"
/// - "←→"
/// - "↓↑"
///
/// The returned Vector of [`ANSIString`] is suitable for transformation into an [`ANSIStrings`]
/// type.
pub fn style_key_with_modifier(
keyvec: &[Key],
palette: &Palette,
background: Option<PaletteColor>,
) -> Vec<ANSIString<'static>> {
// Nothing to do, quit...
if keyvec.is_empty() {
return vec![];
}
let text_color = palette_match!(match palette.theme_hue {
ThemeHue::Dark => palette.white,
ThemeHue::Light => palette.black,
});
let green_color = palette_match!(palette.green);
let orange_color = palette_match!(palette.orange);
let mut ret = vec![];
// Prints modifier key
let modifier_str = match get_common_modifier(keyvec.iter().collect()) {
Some(modifier) => modifier,
None => "".to_string(),
};
let no_modifier = modifier_str.is_empty();
let painted_modifier = if modifier_str.is_empty() {
Style::new().paint("")
} else {
if let Some(background) = background {
let background = palette_match!(background);
Style::new()
.fg(orange_color)
.on(background)
.bold()
.paint(modifier_str)
} else {
Style::new().fg(orange_color).bold().paint(modifier_str)
}
};
ret.push(painted_modifier);
// Prints key group start
let group_start_str = if no_modifier { "<" } else { " + <" };
if let Some(background) = background {
let background = palette_match!(background);
ret.push(
Style::new()
.fg(text_color)
.on(background)
.paint(group_start_str),
);
} else {
ret.push(Style::new().fg(text_color).paint(group_start_str));
}
// Prints the keys
let key = keyvec
.iter()
.map(|key| {
if no_modifier {
format!("{}", key)
} else {
match key {
Key::Ctrl(c) => format!("{}", Key::Char(*c)),
Key::Alt(c) => format!("{}", c),
_ => format!("{}", key),
}
}
})
.collect::<Vec<String>>();
// Special handling of some pre-defined keygroups
let key_string = key.join("");
let key_separator = match &key_string[..] {
"HJKL" => "",
"hjkl" => "",
"←↓↑→" => "",
"←→" => "",
"↓↑" => "",
"[]" => "",
_ => "|",
};
for (idx, key) in key.iter().enumerate() {
if idx > 0 && !key_separator.is_empty() {
if let Some(background) = background {
let background = palette_match!(background);
ret.push(
Style::new()
.fg(text_color)
.on(background)
.paint(key_separator),
);
} else {
ret.push(Style::new().fg(text_color).paint(key_separator));
}
}
if let Some(background) = background {
let background = palette_match!(background);
ret.push(
Style::new()
.fg(green_color)
.on(background)
.bold()
.paint(key.clone()),
);
} else {
ret.push(Style::new().fg(green_color).bold().paint(key.clone()));
}
}
let group_end_str = ">";
if let Some(background) = background {
let background = palette_match!(background);
ret.push(
Style::new()
.fg(text_color)
.on(background)
.paint(group_end_str),
);
} else {
ret.push(Style::new().fg(text_color).paint(group_end_str));
}
ret
}
#[cfg(test)]
pub mod tests {
use super::*;
use ansi_term::unstyle;
use ansi_term::ANSIStrings;
use zellij_tile::prelude::CharOrArrow;
use zellij_tile::prelude::Direction;
fn big_keymap() -> Vec<(Key, Vec<Action>)> {
vec![
(Key::Char('a'), vec![Action::Quit]),
(Key::Ctrl('b'), vec![Action::ScrollUp]),
(Key::Ctrl('d'), vec![Action::ScrollDown]),
(
Key::Alt(CharOrArrow::Char('c')),
vec![Action::ScrollDown, Action::SwitchToMode(InputMode::Normal)],
),
(
Key::Char('1'),
vec![TO_NORMAL, Action::SwitchToMode(InputMode::Locked)],
),
]
}
#[test]
fn common_modifier_with_ctrl_keys() {
let keyvec = vec![Key::Ctrl('a'), Key::Ctrl('b'), Key::Ctrl('c')];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, Some("Ctrl".to_string()));
}
#[test]
fn common_modifier_with_alt_keys_chars() {
let keyvec = vec![
Key::Alt(CharOrArrow::Char('1')),
Key::Alt(CharOrArrow::Char('t')),
Key::Alt(CharOrArrow::Char('z')),
];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, Some("Alt".to_string()));
}
#[test]
fn common_modifier_with_alt_keys_arrows() {
let keyvec = vec![
Key::Alt(CharOrArrow::Direction(Direction::Left)),
Key::Alt(CharOrArrow::Direction(Direction::Right)),
];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, Some("Alt".to_string()));
}
#[test]
fn common_modifier_with_alt_keys_arrows_and_chars() {
let keyvec = vec![
Key::Alt(CharOrArrow::Direction(Direction::Left)),
Key::Alt(CharOrArrow::Direction(Direction::Right)),
Key::Alt(CharOrArrow::Char('t')),
Key::Alt(CharOrArrow::Char('z')),
];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, Some("Alt".to_string()));
}
#[test]
fn common_modifier_with_mixed_alt_ctrl_keys() {
let keyvec = vec![
Key::Alt(CharOrArrow::Direction(Direction::Left)),
Key::Alt(CharOrArrow::Char('z')),
Key::Ctrl('a'),
Key::Ctrl('1'),
];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, None);
}
#[test]
fn common_modifier_with_any_keys() {
let keyvec = vec![Key::Backspace, Key::Char('f'), Key::Down];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, None);
}
#[test]
fn common_modifier_with_ctrl_and_normal_keys() {
let keyvec = vec![Key::Ctrl('a'), Key::Char('f'), Key::Down];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, None);
}
#[test]
fn common_modifier_with_alt_and_normal_keys() {
let keyvec = vec![Key::Alt(CharOrArrow::Char('a')), Key::Char('f'), Key::Down];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, None);
}
#[test]
fn action_key_simple_pattern_match_exact() {
let keymap = &[(Key::Char('f'), vec![Action::Quit])];
let ret = action_key(keymap, &[Action::Quit]);
assert_eq!(ret, vec![Key::Char('f')]);
}
#[test]
fn action_key_simple_pattern_match_pattern_too_long() {
let keymap = &[(Key::Char('f'), vec![Action::Quit])];
let ret = action_key(keymap, &[Action::Quit, Action::ScrollUp]);
assert_eq!(ret, Vec::new());
}
#[test]
fn action_key_simple_pattern_match_pattern_empty() {
let keymap = &[(Key::Char('f'), vec![Action::Quit])];
let ret = action_key(keymap, &[]);
assert_eq!(ret, Vec::new());
}
#[test]
fn action_key_long_pattern_match_exact() {
let keymap = big_keymap();
let ret = action_key(&keymap, &[Action::ScrollDown, TO_NORMAL]);
assert_eq!(ret, vec![Key::Alt(CharOrArrow::Char('c'))]);
}
#[test]
fn action_key_long_pattern_match_too_short() {
let keymap = big_keymap();
let ret = action_key(&keymap, &[TO_NORMAL]);
assert_eq!(ret, Vec::new());
}
#[test]
fn action_key_group_single_pattern() {
let keymap = big_keymap();
let ret = action_key_group(&keymap, &[&[Action::Quit]]);
assert_eq!(ret, vec![Key::Char('a')]);
}
#[test]
fn action_key_group_two_patterns() {
let keymap = big_keymap();
let ret = action_key_group(&keymap, &[&[Action::ScrollDown], &[Action::ScrollUp]]);
// Mind the order!
assert_eq!(ret, vec![Key::Ctrl('d'), Key::Ctrl('b')]);
}
fn get_palette() -> Palette {
Palette::default()
}
#[test]
fn style_key_with_modifier_only_chars() {
let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<a|b|c>".to_string())
}
#[test]
fn style_key_with_modifier_special_group_hjkl() {
let keyvec = vec![
Key::Char('h'),
Key::Char('j'),
Key::Char('k'),
Key::Char('l'),
];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<hjkl>".to_string())
}
#[test]
fn style_key_with_modifier_special_group_hjkl_broken() {
// Sorted the wrong way
let keyvec = vec![
Key::Char('h'),
Key::Char('k'),
Key::Char('j'),
Key::Char('l'),
];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<h|k|j|l>".to_string())
}
#[test]
fn style_key_with_modifier_special_group_all_arrows() {
let keyvec = vec![
Key::Char('←'),
Key::Char('↓'),
Key::Char('↑'),
Key::Char('→'),
];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<←↓↑→>".to_string())
}
#[test]
fn style_key_with_modifier_special_group_left_right_arrows() {
let keyvec = vec![Key::Char('←'), Key::Char('→')];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<←→>".to_string())
}
#[test]
fn style_key_with_modifier_special_group_down_up_arrows() {
let keyvec = vec![Key::Char('↓'), Key::Char('↑')];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<↓↑>".to_string())
}
#[test]
fn style_key_with_modifier_common_ctrl_modifier_chars() {
let keyvec = vec![
Key::Ctrl('a'),
Key::Ctrl('b'),
Key::Ctrl('c'),
Key::Ctrl('d'),
];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "Ctrl + <a|b|c|d>".to_string())
}
#[test]
fn style_key_with_modifier_common_alt_modifier_chars() {
let keyvec = vec![
Key::Alt(CharOrArrow::Char('a')),
Key::Alt(CharOrArrow::Char('b')),
Key::Alt(CharOrArrow::Char('c')),
Key::Alt(CharOrArrow::Char('d')),
];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "Alt + <a|b|c|d>".to_string())
}
#[test]
fn style_key_with_modifier_common_alt_modifier_with_special_group_all_arrows() {
let keyvec = vec![
Key::Alt(CharOrArrow::Direction(Direction::Left)),
Key::Alt(CharOrArrow::Direction(Direction::Down)),
Key::Alt(CharOrArrow::Direction(Direction::Up)),
Key::Alt(CharOrArrow::Direction(Direction::Right)),
];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "Alt + <←↓↑→>".to_string())
}
#[test]
fn style_key_with_modifier_ctrl_alt_char_mixed() {
let keyvec = vec![
Key::Alt(CharOrArrow::Char('a')),
Key::Ctrl('b'),
Key::Char('c'),
];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<Alt+a|Ctrl+b|c>".to_string())
}
#[test]
fn style_key_with_modifier_unprintables() {
let keyvec = vec![
Key::Backspace,
Key::Char('\n'),
Key::Char(' '),
Key::Char('\t'),
Key::PageDown,
Key::Delete,
Key::Home,
Key::End,
Key::Insert,
Key::BackTab,
Key::Esc,
];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(
ret,
"<BACKSPACE|ENTER|SPACE|TAB|PgDn|DEL|HOME|END|INS|TAB|ESC>".to_string()
)
}
#[test]
fn style_key_with_modifier_unprintables_with_common_ctrl_modifier() {
let keyvec = vec![Key::Ctrl('\n'), Key::Ctrl(' '), Key::Ctrl('\t')];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "Ctrl + <ENTER|SPACE|TAB>".to_string())
}
#[test]
fn style_key_with_modifier_unprintables_with_common_alt_modifier() {
let keyvec = vec![
Key::Alt(CharOrArrow::Char('\n')),
Key::Alt(CharOrArrow::Char(' ')),
Key::Alt(CharOrArrow::Char('\t')),
];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "Alt + <ENTER|SPACE|TAB>".to_string())
}
}