* 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
880 lines
30 KiB
Rust
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())
|
|
}
|
|
}
|